From ceb28ace8ba33b31ac2b059ea05befd5a237eceb Mon Sep 17 00:00:00 2001 From: zhongzb <972627721@qq.com> Date: Sat, 22 Apr 2023 21:13:22 +0800 Subject: [PATCH] =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E5=9F=BA=E7=A1=80=E6=90=AD?= =?UTF-8?q?=E5=BB=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mallchat-common/pom.xml | 114 ++ .../mallchat/common/chat/dao/MessageDao.java | 60 + .../common/chat/dao/MessageMarkDao.java | 43 + .../mallchat/common/chat/dao/RoomDao.java | 33 + .../common/chat/domain/entity/Message.java | 96 ++ .../chat/domain/entity/MessageMark.java | 75 ++ .../common/chat/domain/entity/Room.java | 67 + .../domain/enums/MessageMarkTypeEnum.java | 37 + .../chat/domain/enums/MessageStatusEnum.java | 35 + .../chat/domain/enums/MessageTypeEnum.java | 36 + .../chat/domain/enums/RoomTypeEnum.java | 35 + .../common/chat/mapper/MessageMapper.java | 16 + .../common/chat/mapper/MessageMarkMapper.java | 16 + .../common/chat/mapper/RoomMapper.java | 17 + .../chat/service/IMessageMarkService.java | 16 + .../common/chat/service/IMessageService.java | 16 + .../common/chat/service/IRoomService.java | 16 + .../common/annotation/FrequencyControl.java | 59 + .../annotation/FrequencyControlContainer.java | 10 + .../common/aspect/FrequencyControlAspect.java | 91 ++ .../common/common/config/CacheConfig.java | 30 + .../common/config/MybatisPlusConfig.java | 20 + .../common/common/config/RedisConfig.java | 58 + .../common/config/ThreadPoolConfig.java | 59 + .../common/common/constant/MDCKey.java | 6 + .../common/common/constant/RedisKey.java | 33 + .../common/common/domain/dto/RequestInfo.java | 14 + .../common/domain/enums/IdempotentEnum.java | 35 + .../common/domain/enums/YesOrNoEnum.java | 39 + .../domain/vo/request/CursorPageBaseReq.java | 38 + .../common/domain/vo/request/PageBaseReq.java | 30 + .../common/domain/vo/response/ApiResult.java | 58 + .../vo/response/CursorPageBaseResp.java | 54 + .../common/domain/vo/response/IdRespVO.java | 23 + .../domain/vo/response/PageBaseResp.java | 79 ++ .../common/exception/BusinessErrorEnum.java | 32 + .../common/exception/BusinessException.java | 55 + .../common/exception/CommonErrorEnum.java | 31 + .../common/common/exception/ErrorEnum.java | 8 + .../exception/GlobalExceptionHandler.java | 50 + .../common/exception/HttpErrorEnum.java | 51 + .../common/common/utils/AssertUtil.java | 78 ++ .../common/common/utils/CursorUtils.java | 77 ++ .../common/common/utils/JwtUtils.java | 87 ++ .../common/common/utils/RedisUtils.java | 1081 +++++++++++++++++ .../common/common/utils/RequestHolder.java | 25 + .../common/user/dao/ItemConfigDao.java | 26 + .../common/user/dao/UserBackpackDao.java | 60 + .../mallchat/common/user/dao/UserDao.java | 40 + .../common/user/domain/dto/IpResult.java | 26 + .../common/user/domain/entity/IpDetail.java | 33 + .../common/user/domain/entity/IpInfo.java | 63 + .../common/user/domain/entity/ItemConfig.java | 63 + .../common/user/domain/entity/User.java | 103 ++ .../user/domain/entity/UserBackpack.java | 74 ++ .../domain/enums/ChatActiveStatusEnum.java | 35 + .../common/user/domain/enums/ItemEnum.java | 38 + .../user/domain/enums/ItemTypeEnum.java | 35 + .../common/user/mapper/ItemConfigMapper.java | 16 + .../user/mapper/UserBackpackMapper.java | 16 + .../common/user/mapper/UserMapper.java | 16 + .../user/service/IItemConfigService.java | 16 + .../user/service/IUserBackpackService.java | 27 + .../common/user/service/IpService.java | 10 + .../common/user/service/cache/ItemCache.java | 42 + .../common/user/service/cache/UserCache.java | 112 ++ .../user/service/impl/IpServiceImpl.java | 119 ++ .../service/impl/UserBackpackServiceImpl.java | 62 + .../resources/application-test.properties | 21 + .../src/main/resources/application.yml | 60 + .../resources/mapper/chat/MessageMapper.xml | 5 + .../mapper/chat/MessageMarkMapper.xml | 5 + .../main/resources/mapper/chat/RoomMapper.xml | 5 + .../mapper/user/ItemConfigMapper.xml | 5 + .../mapper/user/UserBackpackMapper.xml | 5 + .../main/resources/mapper/user/UserMapper.xml | 5 + mallchat-custom-server/pom.xml | 29 + .../custom/MallchatCustomApplication.java | 22 + .../chat/controller/ChatController.java | 78 ++ .../domain/vo/request/ChatMessageMarkReq.java | 34 + .../domain/vo/request/ChatMessagePageReq.java | 26 + .../domain/vo/request/ChatMessageReq.java | 36 + .../domain/vo/response/ChatMemberResp.java | 36 + .../vo/response/ChatMemberStatisticResp.java | 27 + .../domain/vo/response/ChatMessageResp.java | 94 ++ .../chat/domain/vo/response/ChatRoomResp.java | 30 + .../custom/chat/service/ChatService.java | 68 ++ .../chat/service/adapter/MemberAdapter.java | 46 + .../chat/service/adapter/MessageAdapter.java | 92 ++ .../chat/service/adapter/RoomAdapter.java | 34 + .../chat/service/helper/ChatMemberHelper.java | 30 + .../chat/service/impl/ChatServiceImpl.java | 213 ++++ .../common/config/InterceptorConfig.java | 29 + .../custom/common/config/SwaggerConfig.java | 67 + .../common/config/WxMpConfiguration.java | 84 ++ .../custom/common/config/WxMpProperties.java | 82 ++ .../custom/common/event/MessageMarkEvent.java | 15 + .../custom/common/event/MessageSendEvent.java | 15 + .../custom/common/event/UserOfflineEvent.java | 15 + .../custom/common/event/UserOnlineEvent.java | 17 + .../common/event/UserRegisterEvent.java | 15 + .../event/listener/MessageMarkListener.java | 56 + .../event/listener/MessageSendListener.java | 39 + .../event/listener/UserOfflineListener.java | 52 + .../event/listener/UserOnlineListener.java | 62 + .../event/listener/UserRegisterListener.java | 53 + .../intecepter/CollectorInterceptor.java | 45 + .../common/intecepter/HttpTraceIdFilter.java | 33 + .../common/intecepter/TokenInterceptor.java | 77 ++ .../common/intecepter/WebLogAspect.java | 74 ++ .../user/controller/UserController.java | 60 + .../user/controller/WxPortalController.java | 118 ++ .../user/domain/dto/ws/WSChannelExtraDTO.java | 25 + .../user/domain/enums/WSReqTypeEnum.java | 36 + .../user/domain/enums/WSRespTypeEnum.java | 44 + .../domain/vo/request/user/ModifyNameReq.java | 30 + .../vo/request/user/WearingBadgeReq.java | 29 + .../domain/vo/request/ws/WSAuthorize.java | 20 + .../user/domain/vo/request/ws/WSBaseReq.java | 22 + .../domain/vo/response/user/BadgeResp.java | 30 + .../domain/vo/response/user/UserInfoResp.java | 31 + .../domain/vo/response/ws/WSBaseResp.java | 18 + .../domain/vo/response/ws/WSLoginSuccess.java | 23 + .../domain/vo/response/ws/WSLoginUrl.java | 20 + .../user/domain/vo/response/ws/WSMessage.java | 21 + .../vo/response/ws/WSOnlineOfflineNotify.java | 28 + .../custom/user/service/LoginService.java | 43 + .../custom/user/service/UserService.java | 54 + .../custom/user/service/WebSocketService.java | 63 + .../custom/user/service/WxMsgService.java | 107 ++ .../user/service/adapter/AbstractBuilder.java | 17 + .../user/service/adapter/ImageBuilder.java | 24 + .../user/service/adapter/TextBuilder.java | 21 + .../user/service/adapter/UserAdapter.java | 59 + .../user/service/adapter/WSAdapter.java | 112 ++ .../user/service/handler/AbstractHandler.java | 12 + .../user/service/handler/LogHandler.java | 27 + .../user/service/handler/MsgHandler.java | 52 + .../user/service/handler/ScanHandler.java | 40 + .../service/handler/SubscribeHandler.java | 59 + .../user/service/impl/LoginServiceImpl.java | 82 ++ .../user/service/impl/UserServiceImpl.java | 108 ++ .../service/impl/WebSocketServiceImpl.java | 244 ++++ .../user/websocket/HttpHeadersHandler.java | 29 + .../custom/user/websocket/NettyUtil.java | 27 + .../user/websocket/NettyWebSocketServer.java | 100 ++ .../NettyWebSocketServerHandler.java | 104 ++ .../src/main/resources/logback.xml | 59 + pom.xml | 121 ++ 149 files changed, 8026 insertions(+) create mode 100644 mallchat-common/pom.xml create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/chat/dao/MessageDao.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/chat/dao/MessageMarkDao.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/chat/dao/RoomDao.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/chat/domain/entity/Message.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/chat/domain/entity/MessageMark.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/chat/domain/entity/Room.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/chat/domain/enums/MessageMarkTypeEnum.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/chat/domain/enums/MessageStatusEnum.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/chat/domain/enums/MessageTypeEnum.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/chat/domain/enums/RoomTypeEnum.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/chat/mapper/MessageMapper.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/chat/mapper/MessageMarkMapper.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/chat/mapper/RoomMapper.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/chat/service/IMessageMarkService.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/chat/service/IMessageService.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/chat/service/IRoomService.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/common/annotation/FrequencyControl.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/common/annotation/FrequencyControlContainer.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/common/aspect/FrequencyControlAspect.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/common/config/CacheConfig.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/common/config/MybatisPlusConfig.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/common/config/RedisConfig.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/common/config/ThreadPoolConfig.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/common/constant/MDCKey.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/common/constant/RedisKey.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/common/domain/dto/RequestInfo.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/common/domain/enums/IdempotentEnum.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/common/domain/enums/YesOrNoEnum.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/common/domain/vo/request/CursorPageBaseReq.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/common/domain/vo/request/PageBaseReq.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/common/domain/vo/response/ApiResult.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/common/domain/vo/response/CursorPageBaseResp.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/common/domain/vo/response/IdRespVO.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/common/domain/vo/response/PageBaseResp.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/common/exception/BusinessErrorEnum.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/common/exception/BusinessException.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/common/exception/CommonErrorEnum.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/common/exception/ErrorEnum.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/common/exception/GlobalExceptionHandler.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/common/exception/HttpErrorEnum.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/AssertUtil.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/CursorUtils.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/JwtUtils.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/RedisUtils.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/RequestHolder.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/user/dao/ItemConfigDao.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/user/dao/UserBackpackDao.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/user/dao/UserDao.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/user/domain/dto/IpResult.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/user/domain/entity/IpDetail.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/user/domain/entity/IpInfo.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/user/domain/entity/ItemConfig.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/user/domain/entity/User.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/user/domain/entity/UserBackpack.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/user/domain/enums/ChatActiveStatusEnum.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/user/domain/enums/ItemEnum.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/user/domain/enums/ItemTypeEnum.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/user/mapper/ItemConfigMapper.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/user/mapper/UserBackpackMapper.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/user/mapper/UserMapper.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/user/service/IItemConfigService.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/user/service/IUserBackpackService.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/user/service/IpService.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/user/service/cache/ItemCache.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/user/service/cache/UserCache.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/user/service/impl/IpServiceImpl.java create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/user/service/impl/UserBackpackServiceImpl.java create mode 100644 mallchat-common/src/main/resources/application-test.properties create mode 100644 mallchat-common/src/main/resources/application.yml create mode 100644 mallchat-common/src/main/resources/mapper/chat/MessageMapper.xml create mode 100644 mallchat-common/src/main/resources/mapper/chat/MessageMarkMapper.xml create mode 100644 mallchat-common/src/main/resources/mapper/chat/RoomMapper.xml create mode 100644 mallchat-common/src/main/resources/mapper/user/ItemConfigMapper.xml create mode 100644 mallchat-common/src/main/resources/mapper/user/UserBackpackMapper.xml create mode 100644 mallchat-common/src/main/resources/mapper/user/UserMapper.xml create mode 100644 mallchat-custom-server/pom.xml create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/MallchatCustomApplication.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/controller/ChatController.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/domain/vo/request/ChatMessageMarkReq.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/domain/vo/request/ChatMessagePageReq.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/domain/vo/request/ChatMessageReq.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/domain/vo/response/ChatMemberResp.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/domain/vo/response/ChatMemberStatisticResp.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/domain/vo/response/ChatMessageResp.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/domain/vo/response/ChatRoomResp.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/ChatService.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/adapter/MemberAdapter.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/adapter/MessageAdapter.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/adapter/RoomAdapter.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/helper/ChatMemberHelper.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/impl/ChatServiceImpl.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/config/InterceptorConfig.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/config/SwaggerConfig.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/config/WxMpConfiguration.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/config/WxMpProperties.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/event/MessageMarkEvent.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/event/MessageSendEvent.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/event/UserOfflineEvent.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/event/UserOnlineEvent.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/event/UserRegisterEvent.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/event/listener/MessageMarkListener.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/event/listener/MessageSendListener.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/event/listener/UserOfflineListener.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/event/listener/UserOnlineListener.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/event/listener/UserRegisterListener.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/intecepter/CollectorInterceptor.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/intecepter/HttpTraceIdFilter.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/intecepter/TokenInterceptor.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/intecepter/WebLogAspect.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/controller/UserController.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/controller/WxPortalController.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/dto/ws/WSChannelExtraDTO.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/enums/WSReqTypeEnum.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/enums/WSRespTypeEnum.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/request/user/ModifyNameReq.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/request/user/WearingBadgeReq.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/request/ws/WSAuthorize.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/request/ws/WSBaseReq.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/response/user/BadgeResp.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/response/user/UserInfoResp.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/response/ws/WSBaseResp.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/response/ws/WSLoginSuccess.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/response/ws/WSLoginUrl.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/response/ws/WSMessage.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/response/ws/WSOnlineOfflineNotify.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/LoginService.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/UserService.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/WebSocketService.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/WxMsgService.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/adapter/AbstractBuilder.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/adapter/ImageBuilder.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/adapter/TextBuilder.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/adapter/UserAdapter.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/adapter/WSAdapter.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/handler/AbstractHandler.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/handler/LogHandler.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/handler/MsgHandler.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/handler/ScanHandler.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/handler/SubscribeHandler.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/impl/LoginServiceImpl.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/impl/UserServiceImpl.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/impl/WebSocketServiceImpl.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/websocket/HttpHeadersHandler.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/websocket/NettyUtil.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/websocket/NettyWebSocketServer.java create mode 100644 mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/websocket/NettyWebSocketServerHandler.java create mode 100644 mallchat-custom-server/src/main/resources/logback.xml create mode 100644 pom.xml diff --git a/mallchat-common/pom.xml b/mallchat-common/pom.xml new file mode 100644 index 0000000..cb7d4ca --- /dev/null +++ b/mallchat-common/pom.xml @@ -0,0 +1,114 @@ + + + + mallchat + com.abin.mallchat + 1.0-SNAPSHOT + + 4.0.0 + + mallchat-common + + + + org.springframework.boot + spring-boot-starter + + + org.projectlombok + lombok + + + cn.hutool + hutool-all + + + io.swagger + swagger-annotations + + + + org.mybatis + mybatis + + + com.baomidou + mybatis-plus-boot-starter + + + + com.baomidou + mybatis-plus-generator + + + mybatis-plus-extension + com.baomidou + + + + + org.apache.velocity + velocity-engine-core + 2.0 + + + + mysql + mysql-connector-java + + + + io.jsonwebtoken + jjwt + + + + com.aliyun.oss + aliyun-sdk-oss + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-aop + + + + io.netty + netty-all + + + com.github.binarywang + weixin-java-mp + + + com.github.xiaoymin + + knife4j-spring-boot-starter + 2.0.9 + + + org.hibernate + hibernate-validator + 6.0.1.Final + + + org.springframework.boot + spring-boot-starter-data-redis + + + com.auth0 + java-jwt + 3.19.0 + + + com.github.ben-manes.caffeine + caffeine + + + + \ No newline at end of file diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/chat/dao/MessageDao.java b/mallchat-common/src/main/java/com/abin/mallchat/common/chat/dao/MessageDao.java new file mode 100644 index 0000000..98fbe49 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/chat/dao/MessageDao.java @@ -0,0 +1,60 @@ +package com.abin.mallchat.common.chat.dao; + +import cn.hutool.core.util.StrUtil; +import com.abin.mallchat.common.chat.domain.entity.Message; +import com.abin.mallchat.common.chat.domain.enums.MessageStatusEnum; +import com.abin.mallchat.common.chat.mapper.MessageMapper; +import com.abin.mallchat.common.common.domain.vo.request.CursorPageBaseReq; +import com.abin.mallchat.common.common.domain.vo.response.CursorPageBaseResp; +import com.abin.mallchat.common.common.utils.CursorUtils; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + *

+ * 消息表 服务实现类 + *

+ * + * @author abin + * @since 2023-03-25 + */ +@Service +public class MessageDao extends ServiceImpl { + @Autowired + private CursorUtils cursorUtils; + + public CursorPageBaseResp getCursorPage(Long roomId, CursorPageBaseReq request) { + return cursorUtils.getCursorPageByMysql(this, request, wrapper -> { + wrapper.eq(Message::getRoomId, roomId); + wrapper.eq(Message::getStatus, MessageStatusEnum.NORMAL.getStatus()); + }, Message::getId); + } + + /** + * 乐观更新消息类型 + */ + public boolean riseOptimistic(Long id, Integer oldType, Integer newType) { + return lambdaUpdate() + .eq(Message::getId, id) + .eq(Message::getType, oldType) + .set(Message::getType, newType) + .update(); + } + + public Integer getGapCount(Long roomId, Long fromId, Long toId) { + return lambdaQuery() + .eq(Message::getRoomId, roomId) + .gt(Message::getId, fromId) + .le(Message::getId, toId) + .count(); + } + + public void updateGapCount(Long id, Integer gapCount) { + lambdaUpdate() + .eq(Message::getId, id) + .set(Message::getGapCount, gapCount); + } +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/chat/dao/MessageMarkDao.java b/mallchat-common/src/main/java/com/abin/mallchat/common/chat/dao/MessageMarkDao.java new file mode 100644 index 0000000..f99e4ee --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/chat/dao/MessageMarkDao.java @@ -0,0 +1,43 @@ +package com.abin.mallchat.common.chat.dao; + +import com.abin.mallchat.common.chat.domain.entity.MessageMark; +import com.abin.mallchat.common.chat.mapper.MessageMarkMapper; +import com.abin.mallchat.common.chat.service.IMessageMarkService; +import com.abin.mallchat.common.common.domain.enums.YesOrNoEnum; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + *

+ * 消息标记表 服务实现类 + *

+ * + * @author abin + * @since 2023-04-08 + */ +@Service +public class MessageMarkDao extends ServiceImpl { + + public MessageMark get(Long uid, Long msgId, Integer markType) { + return lambdaQuery().eq(MessageMark::getUid, uid) + .eq(MessageMark::getMsgId, msgId) + .eq(MessageMark::getType, markType) + .one(); + } + + public Integer getMarkCount(Long msgId, Integer markType) { + return lambdaQuery().eq(MessageMark::getMsgId, msgId) + .eq(MessageMark::getType, markType) + .eq(MessageMark::getStatus, YesOrNoEnum.NO.getStatus()) + .count(); + } + + public List getValidMarkByMsgIdBatch(List msgIds) { + return lambdaQuery() + .in(MessageMark::getMsgId, msgIds) + .eq(MessageMark::getStatus, YesOrNoEnum.NO.getStatus()) + .list(); + } +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/chat/dao/RoomDao.java b/mallchat-common/src/main/java/com/abin/mallchat/common/chat/dao/RoomDao.java new file mode 100644 index 0000000..4f2868d --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/chat/dao/RoomDao.java @@ -0,0 +1,33 @@ +package com.abin.mallchat.common.chat.dao; + +import com.abin.mallchat.common.chat.domain.entity.Message; +import com.abin.mallchat.common.chat.domain.entity.Room; +import com.abin.mallchat.common.chat.domain.enums.MessageStatusEnum; +import com.abin.mallchat.common.chat.domain.enums.RoomTypeEnum; +import com.abin.mallchat.common.chat.mapper.RoomMapper; +import com.abin.mallchat.common.common.domain.vo.request.CursorPageBaseReq; +import com.abin.mallchat.common.common.domain.vo.response.CursorPageBaseResp; +import com.abin.mallchat.common.common.utils.CursorUtils; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + *

+ * 会话表 服务实现类 + *

+ * + * @author abin + * @since 2023-03-25 + */ +@Service +public class RoomDao extends ServiceImpl { + @Autowired + private CursorUtils cursorUtils; + + public CursorPageBaseResp getCursorPage(CursorPageBaseReq request) { + return cursorUtils.getCursorPageByMysql(this, request, wrapper -> { + wrapper.ne(Room::getType, RoomTypeEnum.GROUP.getStatus()); + }, Room::getActiveTime); + } +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/chat/domain/entity/Message.java b/mallchat-common/src/main/java/com/abin/mallchat/common/chat/domain/entity/Message.java new file mode 100644 index 0000000..ec81daf --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/chat/domain/entity/Message.java @@ -0,0 +1,96 @@ +package com.abin.mallchat.common.chat.domain.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.baomidou.mybatisplus.annotation.TableField; +import java.io.Serializable; +import java.util.Date; + +import lombok.*; +import lombok.experimental.Accessors; + +/** + *

+ * 消息表 + *

+ * + * @author abin + * @since 2023-03-25 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("message") +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class Message implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * id + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 会话表id + */ + @TableField("room_id") + private Long roomId; + + /** + * 消息发送者uid + */ + @TableField("from_uid") + private Long fromUid; + + /** + * 消息内容 + */ + @TableField("content") + private String content; + + /** + * 回复的消息内容 + */ + @TableField("reply_msg_id") + private Long replyMsgId; + + /** + * 消息状态 0正常 1删除 + * @see com.abin.mallchat.common.chat.domain.enums.MessageStatusEnum + */ + @TableField("status") + private Integer status; + + /** + * 与回复消息的间隔条数 + */ + @TableField("gap_count") + private Integer gapCount; + + /** + * 消息类型 1正常文本 2.爆赞 (点赞超过10)3.危险发言(举报超5) + * @see com.abin.mallchat.common.chat.domain.enums.MessageTypeEnum + */ + @TableField("type") + private Integer type; + + + /** + * 创建时间 + */ + @TableField("create_time") + private Date createTime; + + /** + * 修改时间 + */ + @TableField("update_time") + private Date updateTime; + + +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/chat/domain/entity/MessageMark.java b/mallchat-common/src/main/java/com/abin/mallchat/common/chat/domain/entity/MessageMark.java new file mode 100644 index 0000000..0f27280 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/chat/domain/entity/MessageMark.java @@ -0,0 +1,75 @@ +package com.abin.mallchat.common.chat.domain.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.baomidou.mybatisplus.annotation.TableField; +import java.io.Serializable; + +import lombok.*; +import lombok.experimental.Accessors; + +/** + *

+ * 消息标记表 + *

+ * + * @author abin + * @since 2023-04-08 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("message_mark") +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class MessageMark implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * id + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 消息表id + */ + @TableField("msg_id") + private Long msgId; + + /** + * 标记人uid + */ + @TableField("uid") + private Long uid; + + /** + * 标记类型 1点赞 2举报 + * @see com.abin.mallchat.common.chat.domain.enums.MessageMarkTypeEnum + */ + @TableField("type") + private Integer type; + + /** + * 消息状态 0正常 1取消 + */ + @TableField("status") + private Integer status; + + /** + * 创建时间 + */ + @TableField("create_time") + private LocalDateTime createTime; + + /** + * 修改时间 + */ + @TableField("update_time") + private LocalDateTime updateTime; + + +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/chat/domain/entity/Room.java b/mallchat-common/src/main/java/com/abin/mallchat/common/chat/domain/entity/Room.java new file mode 100644 index 0000000..12e8ac4 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/chat/domain/entity/Room.java @@ -0,0 +1,67 @@ +package com.abin.mallchat.common.chat.domain.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.baomidou.mybatisplus.annotation.TableField; +import java.io.Serializable; +import java.util.Date; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + *

+ * 会话表 + *

+ * + * @author abin + * @since 2023-03-25 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("room") +public class Room implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * id + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 会话名 + */ + @TableField("name") + private String name; + + /** + * 会话类型 1大群聊 2沸点 + * @see com.abin.mallchat.common.chat.domain.enums.RoomTypeEnum + */ + @TableField("type") + private Integer type; + + /** + * 最后活跃时间-排序 + */ + @TableField("active_time") + private Date activeTime; + + /** + * 创建时间 + */ + @TableField("create_time") + private Date createTime; + + /** + * 修改时间 + */ + @TableField("update_time") + private Date updateTime; + + +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/chat/domain/enums/MessageMarkTypeEnum.java b/mallchat-common/src/main/java/com/abin/mallchat/common/chat/domain/enums/MessageMarkTypeEnum.java new file mode 100644 index 0000000..5b77fdb --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/chat/domain/enums/MessageMarkTypeEnum.java @@ -0,0 +1,37 @@ +package com.abin.mallchat.common.chat.domain.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * Description: 消息标记类型 + * Author: abin + * Date: 2023-03-19 + */ +@AllArgsConstructor +@Getter +public enum MessageMarkTypeEnum { + LIKE(1, "点赞",10,MessageTypeEnum.LIKE), + DISLIKE(2, "点踩",5,MessageTypeEnum.DISLIKE), + ; + + private final Integer type; + private final String desc; + private final Integer riseNum;//需要多少个标记升级 + private final MessageTypeEnum riseEnum;//消息升级成什么类型的消息 + + private static Map cache; + + static { + cache = Arrays.stream(MessageMarkTypeEnum.values()).collect(Collectors.toMap(MessageMarkTypeEnum::getType, Function.identity())); + } + + public static MessageMarkTypeEnum of(Integer type) { + return cache.get(type); + } +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/chat/domain/enums/MessageStatusEnum.java b/mallchat-common/src/main/java/com/abin/mallchat/common/chat/domain/enums/MessageStatusEnum.java new file mode 100644 index 0000000..a58a723 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/chat/domain/enums/MessageStatusEnum.java @@ -0,0 +1,35 @@ +package com.abin.mallchat.common.chat.domain.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * Description: 消息状态 + * Author: abin + * Date: 2023-03-19 + */ +@AllArgsConstructor +@Getter +public enum MessageStatusEnum { + NORMAL(0, "正常"), + DELETE(1, "删除"), + ; + + private final Integer status; + private final String desc; + + private static Map cache; + + static { + cache = Arrays.stream(MessageStatusEnum.values()).collect(Collectors.toMap(MessageStatusEnum::getStatus, Function.identity())); + } + + public static MessageStatusEnum of(Integer type) { + return cache.get(type); + } +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/chat/domain/enums/MessageTypeEnum.java b/mallchat-common/src/main/java/com/abin/mallchat/common/chat/domain/enums/MessageTypeEnum.java new file mode 100644 index 0000000..0470531 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/chat/domain/enums/MessageTypeEnum.java @@ -0,0 +1,36 @@ +package com.abin.mallchat.common.chat.domain.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * Description: 消息状态 + * Author: abin + * Date: 2023-03-19 + */ +@AllArgsConstructor +@Getter +public enum MessageTypeEnum { + NORMAL(1, "正常"), + LIKE(2, "爆赞"), + DISLIKE(3, "危险发言"), + ; + + private final Integer type; + private final String desc; + + private static Map cache; + + static { + cache = Arrays.stream(MessageTypeEnum.values()).collect(Collectors.toMap(MessageTypeEnum::getType, Function.identity())); + } + + public static MessageTypeEnum of(Integer type) { + return cache.get(type); + } +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/chat/domain/enums/RoomTypeEnum.java b/mallchat-common/src/main/java/com/abin/mallchat/common/chat/domain/enums/RoomTypeEnum.java new file mode 100644 index 0000000..c255add --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/chat/domain/enums/RoomTypeEnum.java @@ -0,0 +1,35 @@ +package com.abin.mallchat.common.chat.domain.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * Description: 消息状态 + * Author: abin + * Date: 2023-03-19 + */ +@AllArgsConstructor +@Getter +public enum RoomTypeEnum { + GROUP(1, "大群聊"), + BOILING(2, "沸点"), + ; + + private final Integer status; + private final String desc; + + private static Map cache; + + static { + cache = Arrays.stream(RoomTypeEnum.values()).collect(Collectors.toMap(RoomTypeEnum::getStatus, Function.identity())); + } + + public static RoomTypeEnum of(Integer type) { + return cache.get(type); + } +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/chat/mapper/MessageMapper.java b/mallchat-common/src/main/java/com/abin/mallchat/common/chat/mapper/MessageMapper.java new file mode 100644 index 0000000..62d7c17 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/chat/mapper/MessageMapper.java @@ -0,0 +1,16 @@ +package com.abin.mallchat.common.chat.mapper; + +import com.abin.mallchat.common.chat.domain.entity.Message; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * 消息表 Mapper 接口 + *

+ * + * @author abin + * @since 2023-03-25 + */ +public interface MessageMapper extends BaseMapper { + +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/chat/mapper/MessageMarkMapper.java b/mallchat-common/src/main/java/com/abin/mallchat/common/chat/mapper/MessageMarkMapper.java new file mode 100644 index 0000000..9889838 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/chat/mapper/MessageMarkMapper.java @@ -0,0 +1,16 @@ +package com.abin.mallchat.common.chat.mapper; + +import com.abin.mallchat.common.chat.domain.entity.MessageMark; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * 消息标记表 Mapper 接口 + *

+ * + * @author abin + * @since 2023-04-08 + */ +public interface MessageMarkMapper extends BaseMapper { + +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/chat/mapper/RoomMapper.java b/mallchat-common/src/main/java/com/abin/mallchat/common/chat/mapper/RoomMapper.java new file mode 100644 index 0000000..5eb89ad --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/chat/mapper/RoomMapper.java @@ -0,0 +1,17 @@ +package com.abin.mallchat.common.chat.mapper; + + +import com.abin.mallchat.common.chat.domain.entity.Room; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * 会话表 Mapper 接口 + *

+ * + * @author abin + * @since 2023-03-25 + */ +public interface RoomMapper extends BaseMapper { + +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/chat/service/IMessageMarkService.java b/mallchat-common/src/main/java/com/abin/mallchat/common/chat/service/IMessageMarkService.java new file mode 100644 index 0000000..fce8c40 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/chat/service/IMessageMarkService.java @@ -0,0 +1,16 @@ +package com.abin.mallchat.common.chat.service; + +import com.abin.mallchat.common.chat.domain.entity.MessageMark; +import com.baomidou.mybatisplus.extension.service.IService; + +/** + *

+ * 消息标记表 服务类 + *

+ * + * @author abin + * @since 2023-04-08 + */ +public interface IMessageMarkService extends IService { + +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/chat/service/IMessageService.java b/mallchat-common/src/main/java/com/abin/mallchat/common/chat/service/IMessageService.java new file mode 100644 index 0000000..91e0e2d --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/chat/service/IMessageService.java @@ -0,0 +1,16 @@ +package com.abin.mallchat.common.chat.service; + +import com.abin.mallchat.common.chat.domain.entity.Message; +import com.baomidou.mybatisplus.extension.service.IService; + +/** + *

+ * 消息表 服务类 + *

+ * + * @author abin + * @since 2023-03-25 + */ +public interface IMessageService extends IService { + +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/chat/service/IRoomService.java b/mallchat-common/src/main/java/com/abin/mallchat/common/chat/service/IRoomService.java new file mode 100644 index 0000000..3cf91fe --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/chat/service/IRoomService.java @@ -0,0 +1,16 @@ +package com.abin.mallchat.common.chat.service; + +import com.abin.mallchat.common.chat.domain.entity.Room; +import com.baomidou.mybatisplus.extension.service.IService; + +/** + *

+ * 会话表 服务类 + *

+ * + * @author abin + * @since 2023-03-25 + */ +public interface IRoomService extends IService { + +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/annotation/FrequencyControl.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/annotation/FrequencyControl.java new file mode 100644 index 0000000..a45c8c4 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/annotation/FrequencyControl.java @@ -0,0 +1,59 @@ +package com.abin.mallchat.common.common.annotation; + +import java.lang.annotation.*; +import java.util.concurrent.TimeUnit; + +/** + * 频控注解 + */ +@Repeatable(FrequencyControlContainer.class)//可重复 +@Retention(RetentionPolicy.RUNTIME)//运行时生效 +@Target(ElementType.METHOD)//作用在方法上 +public @interface FrequencyControl { + /** + * key的前缀,默认取方法全限定名,除非我们在不同方法上对同一个资源做频控,就自己指定 + * + * @return key的前缀 + */ + String prefixKey() default ""; + + /** + * 频控对象,默认el表达指定具体的频控对象 + * 对于ip 和uid模式,需要是http入口的对象,保证RequestHolder里有值 + * + * @return 对象 + */ + Target target() default Target.EL; + + /** + * springEl 表达式,target=EL必填 + * + * @return 表达式 + */ + String spEl() default ""; + + /** + * 频控时间范围,默认单位秒 + * + * @return 时间范围 + */ + int time(); + + /** + * 频控时间单位,默认秒 + * + * @return 单位 + */ + TimeUnit unit() default TimeUnit.SECONDS; + + /** + * 单位时间内最大访问次数 + * + * @return 次数 + */ + int count(); + + enum Target { + UID, IP, EL + } +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/annotation/FrequencyControlContainer.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/annotation/FrequencyControlContainer.java new file mode 100644 index 0000000..ad6de09 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/annotation/FrequencyControlContainer.java @@ -0,0 +1,10 @@ +package com.abin.mallchat.common.common.annotation; + +import java.lang.annotation.*; + + +@Retention(RetentionPolicy.RUNTIME)//运行时生效 +@Target(ElementType.METHOD)//作用在方法上 +public @interface FrequencyControlContainer { + FrequencyControl[] value(); +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/aspect/FrequencyControlAspect.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/aspect/FrequencyControlAspect.java new file mode 100644 index 0000000..3347dc6 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/aspect/FrequencyControlAspect.java @@ -0,0 +1,91 @@ +package com.abin.mallchat.common.common.aspect; + +import cn.hutool.core.util.StrUtil; +import com.abin.mallchat.common.common.annotation.FrequencyControl; +import com.abin.mallchat.common.common.exception.BusinessException; +import com.abin.mallchat.common.common.exception.CommonErrorEnum; +import com.abin.mallchat.common.common.utils.RedisUtils; +import com.abin.mallchat.common.common.utils.RequestHolder; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.DefaultParameterNameDiscoverer; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.Expression; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.stereotype.Component; +import java.lang.reflect.Method; +import java.util.*; + +/** + * Description: 频控实现 + * Author: abin + * Date: 2023-04-20 + */ +@Slf4j +@Aspect +@Component +public class FrequencyControlAspect { + @Autowired + private RedisUtils redisUtils; + private final ExpressionParser parser = new SpelExpressionParser(); + private final DefaultParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer(); + + @Around("@annotation(com.abin.mallchat.common.common.annotation.FrequencyControl)||@annotation(com.abin.mallchat.common.common.annotation.FrequencyControlContainer)") + public Object around(ProceedingJoinPoint joinPoint) throws Throwable { + Method method = ((MethodSignature) joinPoint.getSignature()).getMethod(); + FrequencyControl[] annotationsByType = method.getAnnotationsByType(FrequencyControl.class); + Map keyMap = new HashMap<>(); + for (int i = 0; i < annotationsByType.length; i++) { + FrequencyControl frequencyControl = annotationsByType[i]; + String prefix = StrUtil.isBlank(frequencyControl.prefixKey()) ? method.toGenericString() + ":index:" + i : frequencyControl.prefixKey();//默认方法限定名+注解排名(可能多个) + String key = ""; + switch (frequencyControl.target()) { + case EL: + key = parseSpEl(method, joinPoint.getArgs(), frequencyControl.spEl()); + break; + case IP: + key = RequestHolder.get().getIp(); + break; + case UID: + key = RequestHolder.get().getUid().toString(); + } + keyMap.put(prefix + ":" + key, frequencyControl); + } + //批量获取redis统计的值 + ArrayList keyList = new ArrayList<>(keyMap.keySet()); + List countList = redisUtils.mget(keyList, Integer.class); + for (int i = 0; i < keyList.size(); i++) { + String key = keyList.get(i); + Integer count = countList.get(i); + FrequencyControl frequencyControl = keyMap.get(key); + if (Objects.nonNull(count) && count >= frequencyControl.count()) {//频率超过了 + log.warn("frequencyControl limit key:{},count:{}", key, count); + throw new BusinessException(CommonErrorEnum.FREQUENCY_LIMIT); + } + } + try { + return joinPoint.proceed(); + } finally { + //不管成功还是失败,都增加次数 + keyMap.forEach((k, v) -> { + redisUtils.inc(k,v.time(),v.unit()); + }); + } + } + + private String parseSpEl(Method method, Object[] args, String spEl) { + String[] params = parameterNameDiscoverer.getParameterNames(method);//解析参数名 + EvaluationContext context = new StandardEvaluationContext();//el解析需要的上下文对象 + for (int i = 0; i < params.length; i++) { + context.setVariable(params[i], args[i]);//所有参数都作为原材料扔进去 + } + Expression expression = parser.parseExpression(spEl); + return expression.getValue(context, String.class); + } +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/config/CacheConfig.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/config/CacheConfig.java new file mode 100644 index 0000000..1a7aec5 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/config/CacheConfig.java @@ -0,0 +1,30 @@ +package com.abin.mallchat.common.common.config; + +import com.github.benmanes.caffeine.cache.Caffeine; +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.CachingConfigurerSupport; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.cache.caffeine.CaffeineCacheManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; + +import java.util.concurrent.TimeUnit; + +@EnableCaching +@Configuration +public class CacheConfig extends CachingConfigurerSupport { + + @Bean("caffeineCacheManager") + @Primary + public CacheManager caffeineCacheManager() { + CaffeineCacheManager cacheManager = new CaffeineCacheManager(); + // 方案一(常用):定制化缓存Cache + cacheManager.setCaffeine(Caffeine.newBuilder() + .expireAfterWrite(10, TimeUnit.MINUTES) + .initialCapacity(100) + .maximumSize(200)); + return cacheManager; + } + +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/config/MybatisPlusConfig.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/config/MybatisPlusConfig.java new file mode 100644 index 0000000..339c2c1 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/config/MybatisPlusConfig.java @@ -0,0 +1,20 @@ +package com.abin.mallchat.common.common.config; + +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class MybatisPlusConfig { + /** + * 新增分页拦截器,并设置数据库类型为mysql + */ + @Bean + public MybatisPlusInterceptor mybatisPlusInterceptor() { + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); + return interceptor; + } +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/config/RedisConfig.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/config/RedisConfig.java new file mode 100644 index 0000000..0bdf4b0 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/config/RedisConfig.java @@ -0,0 +1,58 @@ +package com.abin.mallchat.common.common.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.RedisSerializer; +import org.springframework.data.redis.serializer.SerializationException; +import org.springframework.data.redis.serializer.SerializationUtils; +import org.springframework.util.Assert; + +import java.net.UnknownHostException; +import java.util.Objects; + +@Configuration +public class RedisConfig { + @Bean + public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { + // 创建模板 + RedisTemplate redisTemplate = new RedisTemplate<>(); + // 设置连接工厂 + redisTemplate.setConnectionFactory(redisConnectionFactory); + // 设置序列化工具 + MyRedisSerializerCustomized jsonRedisSerializer = + new MyRedisSerializerCustomized(); + // key和 hashKey采用 string序列化 + redisTemplate.setKeySerializer(RedisSerializer.string()); + redisTemplate.setHashKeySerializer(RedisSerializer.string()); + // value和 hashValue采用 JSON序列化 + redisTemplate.setValueSerializer(jsonRedisSerializer); + redisTemplate.setHashValueSerializer(jsonRedisSerializer); + return redisTemplate; + } + public class MyRedisSerializerCustomized extends GenericJackson2JsonRedisSerializer { + @Override + public byte[] serialize(Object source) throws SerializationException { + if (Objects.nonNull(source)) { + if (source instanceof String || source instanceof Character) { + return source.toString().getBytes(); + } + } + return super.serialize(source); + } + @Override + public T deserialize(byte[] source, Class type) throws SerializationException { + Assert.notNull(type, + "Deserialization type must not be null! Please provide Object.class to make use of Jackson2 default typing."); + if (source == null || source.length == 0) { + return null; + } + if (type.isInstance(String.class) || type.isInstance(Character.class)) { + return (T) new String(source); + } + return super.deserialize(source, type); + } + } +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/config/ThreadPoolConfig.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/config/ThreadPoolConfig.java new file mode 100644 index 0000000..9650c0a --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/config/ThreadPoolConfig.java @@ -0,0 +1,59 @@ +package com.abin.mallchat.common.common.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.scheduling.annotation.AsyncConfigurer; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import java.util.concurrent.Executor; +import java.util.concurrent.ThreadPoolExecutor; + +/** + * Description: 线程池配置 + * Author: abin + * Date: 2023-04-09 + */ +@Configuration +@EnableAsync +public class ThreadPoolConfig implements AsyncConfigurer { + /** + * 项目共用线程池 + */ + public static final String MALLCHAT_EXECUTOR = "mallchatExecutor"; + /** + * websocket通信线程池 + */ + public static final String WS_EXECUTOR = "websocketExecutor"; + + @Override + public Executor getAsyncExecutor() { + return mallchatExecutor(); + } + + @Bean(MALLCHAT_EXECUTOR) + @Primary + public ThreadPoolTaskExecutor mallchatExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(10); + executor.setMaxPoolSize(10); + executor.setQueueCapacity(200); + executor.setThreadNamePrefix("mallchat-executor-"); + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());//满了调用线程执行,认为重要任务 + executor.initialize(); + return executor; + } + + @Bean(WS_EXECUTOR) + public ThreadPoolTaskExecutor websocketExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(16); + executor.setMaxPoolSize(16); + executor.setQueueCapacity(1000);//支持同时推送1000人 + executor.setThreadNamePrefix("websocket-executor-"); + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());//满了直接丢弃,默认为不重要消息推送 + executor.initialize(); + return executor; + } +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/constant/MDCKey.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/constant/MDCKey.java new file mode 100644 index 0000000..7e32302 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/constant/MDCKey.java @@ -0,0 +1,6 @@ +package com.abin.mallchat.common.common.constant; + +public interface MDCKey { + String TID = "tid"; + String UID = "uid"; +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/constant/RedisKey.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/constant/RedisKey.java new file mode 100644 index 0000000..c0c5efc --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/constant/RedisKey.java @@ -0,0 +1,33 @@ +package com.abin.mallchat.common.common.constant; + +/** + * @author zhongzb create on 2021/06/10 + */ +public class RedisKey { + private static final String BASE_KEY = "mallchat:"; + + /** + * 在线用户列表 + */ + public static final String ONLINE_UID_ZET = "online"; + + /** + * 离线用户列表 + */ + public static final String OFFLINE_UID_ZET = "offline"; + + /** + * 用户信息 + */ + public static final String USER_INFO_STRING = "userInfo:uid_%d"; + + /** + * 用户token存放 + */ + public static final String USER_TOKEN_STRING = "userToken:uid_%d"; + + public static String getKey(String key, Object... objects) { + return BASE_KEY + String.format(key, objects); + } + +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/domain/dto/RequestInfo.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/domain/dto/RequestInfo.java new file mode 100644 index 0000000..6faa555 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/domain/dto/RequestInfo.java @@ -0,0 +1,14 @@ +package com.abin.mallchat.common.common.domain.dto; + +import lombok.Data; + +/** + * Description: web请求信息收集类 + * Author: abin + * Date: 2023-04-05 + */ +@Data +public class RequestInfo { + private Long uid; + private String ip; +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/domain/enums/IdempotentEnum.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/domain/enums/IdempotentEnum.java new file mode 100644 index 0000000..a0f75b5 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/domain/enums/IdempotentEnum.java @@ -0,0 +1,35 @@ +package com.abin.mallchat.common.common.domain.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * Description: 幂等类型 + * Author: abin + * Date: 2023-03-19 + */ +@AllArgsConstructor +@Getter +public enum IdempotentEnum { + UID(1, "uid"), + MSG_ID(2, "消息id"), + ; + + private final Integer type; + private final String desc; + + private static Map cache; + + static { + cache = Arrays.stream(IdempotentEnum.values()).collect(Collectors.toMap(IdempotentEnum::getType, Function.identity())); + } + + public static IdempotentEnum of(Integer type) { + return cache.get(type); + } +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/domain/enums/YesOrNoEnum.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/domain/enums/YesOrNoEnum.java new file mode 100644 index 0000000..ce344ea --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/domain/enums/YesOrNoEnum.java @@ -0,0 +1,39 @@ +package com.abin.mallchat.common.common.domain.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * Description: ws前端请求类型枚举 + * Author: abin + * Date: 2023-03-19 + */ +@AllArgsConstructor +@Getter +public enum YesOrNoEnum { + NO(0, "否"), + YES(1, "是"), + ; + + private final Integer status; + private final String desc; + + private static Map cache; + + static { + cache = Arrays.stream(YesOrNoEnum.values()).collect(Collectors.toMap(YesOrNoEnum::getStatus, Function.identity())); + } + + public static YesOrNoEnum of(Integer type) { + return cache.get(type); + } + + public static Integer toStatus(Boolean bool){ + return bool?YES.getStatus():NO.getStatus(); + } +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/domain/vo/request/CursorPageBaseReq.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/domain/vo/request/CursorPageBaseReq.java new file mode 100644 index 0000000..ab7d066 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/domain/vo/request/CursorPageBaseReq.java @@ -0,0 +1,38 @@ +package com.abin.mallchat.common.common.domain.vo.request; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.generator.config.querys.XuguQuery; +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Objects; + +/** + * @author abin + * @since 2023-03-19 + */ +@Data +@ApiModel("游标翻页请求") +@AllArgsConstructor +@NoArgsConstructor +public class CursorPageBaseReq { + + @ApiModelProperty("页面大小") + private Integer pageSize = 10; + + @ApiModelProperty("游标(初始为null,后续请求附带上次翻页的游标)") + private String cursor; + + public Page plusPage() { + return new Page(1, this.pageSize); + } + + @JsonIgnore + public Boolean isFirstPage() { + return Objects.isNull(cursor); + } +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/domain/vo/request/PageBaseReq.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/domain/vo/request/PageBaseReq.java new file mode 100644 index 0000000..f55636f --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/domain/vo/request/PageBaseReq.java @@ -0,0 +1,30 @@ +package com.abin.mallchat.common.common.domain.vo.request; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * @author abin + * @since 2023-03-19 + */ +@Data +@ApiModel("基础翻页请求") +public class PageBaseReq{ + + @ApiModelProperty("页面大小") + private Integer pageSize = 10; + + @ApiModelProperty("页面索引(从1开始)") + private Integer pageNo = 1; + + /** + * 获取mybatisPlus的page + * + * @return + */ + public Page plusPage() { + return new Page(pageNo, pageSize); + } +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/domain/vo/response/ApiResult.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/domain/vo/response/ApiResult.java new file mode 100644 index 0000000..b3528af --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/domain/vo/response/ApiResult.java @@ -0,0 +1,58 @@ +package com.abin.mallchat.common.common.domain.vo.response; + +import com.abin.mallchat.common.common.exception.ErrorEnum; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * Description: 通用返回体 + * Author: abin + * Date: 2023-03-23 + */ +@Data +@ApiModel("基础返回体") +public class ApiResult { + @ApiModelProperty("成功标识true or false") + private Boolean success; + @ApiModelProperty("错误码") + private Integer errCode; + @ApiModelProperty("错误消息") + private String errMsg; + @ApiModelProperty("返回对象") + private T data; + + public static ApiResult success() { + ApiResult result = new ApiResult(); + result.setData(null); + result.setSuccess(Boolean.TRUE); + return result; + } + + public static ApiResult success(T data) { + ApiResult result = new ApiResult(); + result.setData(data); + result.setSuccess(Boolean.TRUE); + return result; + } + + public static ApiResult fail(Integer code, String msg) { + ApiResult result = new ApiResult(); + result.setSuccess(Boolean.FALSE); + result.setErrCode(code); + result.setErrMsg(msg); + return result; + } + + public static ApiResult fail(ErrorEnum errorEnum) { + ApiResult result = new ApiResult(); + result.setSuccess(Boolean.FALSE); + result.setErrCode(errorEnum.getErrorCode()); + result.setErrMsg(errorEnum.getErrorMsg()); + return result; + } + + public boolean isSuccess() { + return this.success; + } +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/domain/vo/response/CursorPageBaseResp.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/domain/vo/response/CursorPageBaseResp.java new file mode 100644 index 0000000..2be47a2 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/domain/vo/response/CursorPageBaseResp.java @@ -0,0 +1,54 @@ +package com.abin.mallchat.common.common.domain.vo.response; + +import cn.hutool.core.collection.CollectionUtil; +import com.abin.mallchat.common.chat.domain.entity.Message; +import com.abin.mallchat.common.common.domain.vo.request.CursorPageBaseReq; +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author abin + * @since 2023-03-19 + */ +@Data +@ApiModel("游标翻页返回") +@AllArgsConstructor +@NoArgsConstructor +public class CursorPageBaseResp { + + @ApiModelProperty("游标(下次翻页带上这参数)") + private String cursor; + + @ApiModelProperty("是否最后一页") + private Boolean isLast = Boolean.FALSE; + + @ApiModelProperty("数据列表") + private List list; + + public static CursorPageBaseResp init(CursorPageBaseResp cursorPage, List list) { + CursorPageBaseResp cursorPageBaseResp = new CursorPageBaseResp(); + cursorPageBaseResp.setIsLast(cursorPage.getIsLast()); + cursorPageBaseResp.setList(list); + cursorPageBaseResp.setCursor(cursorPage.getCursor()); + return cursorPageBaseResp; + } + @JsonIgnore + public Boolean isEmpty() { + return CollectionUtil.isEmpty(list); + } + + public static CursorPageBaseResp empty() { + CursorPageBaseResp cursorPageBaseResp = new CursorPageBaseResp(); + cursorPageBaseResp.setIsLast(true); + cursorPageBaseResp.setList(new ArrayList()); + return cursorPageBaseResp; + } + +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/domain/vo/response/IdRespVO.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/domain/vo/response/IdRespVO.java new file mode 100644 index 0000000..c0bff16 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/domain/vo/response/IdRespVO.java @@ -0,0 +1,23 @@ +package com.abin.mallchat.common.common.domain.vo.response; + +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author zhongzb create on 2021/05/31 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class IdRespVO { + @ApiModelProperty("id") + private long id; + + public static IdRespVO id(Long id) { + IdRespVO idRespVO = new IdRespVO(); + idRespVO.setId(id); + return idRespVO; + } +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/domain/vo/response/PageBaseResp.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/domain/vo/response/PageBaseResp.java new file mode 100644 index 0000000..144a390 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/domain/vo/response/PageBaseResp.java @@ -0,0 +1,79 @@ +package com.abin.mallchat.common.common.domain.vo.response; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author abin + * @since 2023-03-19 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@ApiModel("基础翻页返回") +public class PageBaseResp { + + @ApiModelProperty("当前页数") + private Integer pageNo; + + @ApiModelProperty("每页查询数量") + private Integer pageSize; + + @ApiModelProperty("总记录数") + private Long totalRecords; + + @ApiModelProperty("是否最后一页") + private Boolean isLast = Boolean.FALSE; + + @ApiModelProperty("数据列表") + private List list; + + + public static PageBaseResp empty() { + PageBaseResp r = new PageBaseResp(); + r.setPageNo(1); + r.setPageSize(0); + r.setIsLast(true); + r.setTotalRecords(0L); + r.setList(new ArrayList()); + return r; + } + + public static PageBaseResp init(Integer pageNo, Integer pageSize, Long totalRecords, Boolean isLast, List list) { + return new PageBaseResp<>(pageNo, pageSize, totalRecords, isLast, list); + } + + public static PageBaseResp init(Integer pageNo, Integer pageSize, Long totalRecords, List list) { + return new PageBaseResp<>(pageNo, pageSize, totalRecords, isLastPage(totalRecords, pageNo, pageSize), list); + } + + public static PageBaseResp init(IPage page) { + return init((int) page.getCurrent(), (int) page.getSize(), page.getTotal(), page.getRecords()); + } + + public static PageBaseResp init(IPage page, List list) { + return init((int) page.getCurrent(), (int) page.getSize(), page.getTotal(), list); + } + + public static PageBaseResp init(PageBaseResp resp, List list) { + return init(resp.getPageNo(), resp.getPageSize(), resp.getTotalRecords(), resp.getIsLast(), list); + } + + /** + * 是否是最后一页 + */ + public static Boolean isLastPage(long totalRecords, int pageNo, int pageSize) { + if (pageSize == 0) { + return false; + } + long pageTotal = totalRecords / pageSize + (totalRecords % pageSize == 0 ? 0 : 1); + return pageNo >= pageTotal ? true : false; + } +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/exception/BusinessErrorEnum.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/exception/BusinessErrorEnum.java new file mode 100644 index 0000000..c082a45 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/exception/BusinessErrorEnum.java @@ -0,0 +1,32 @@ +package com.abin.mallchat.common.common.exception; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * Description: 业务校验异常码 + * Author: abin + * Date: 2023-03-26 + */ +@AllArgsConstructor +@Getter +public enum BusinessErrorEnum implements ErrorEnum { + //==================================common================================== + BUSINESS_ERROR(1001, "{}"), + //==================================user================================== + //==================================chat================================== + SYSTEM_ERROR(1001, "系统出小差了,请稍后再试哦~~"), + ; + private Integer code; + private String msg; + + @Override + public Integer getErrorCode() { + return code; + } + + @Override + public String getErrorMsg() { + return msg; + } +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/exception/BusinessException.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/exception/BusinessException.java new file mode 100644 index 0000000..3cf2588 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/exception/BusinessException.java @@ -0,0 +1,55 @@ +package com.abin.mallchat.common.common.exception; + +import lombok.Data; + +@Data +public class BusinessException extends RuntimeException { + private static final long serialVersionUID = 1L; + + /** + *  错误码 + */ + protected Integer errorCode; + + /** + *  错误信息 + */ + protected String errorMsg; + + public BusinessException() { + super(); + } + + public BusinessException(String errorMsg) { + super(errorMsg); + this.errorMsg = errorMsg; + } + + public BusinessException(Integer errorCode, String errorMsg) { + super(errorMsg); + this.errorCode = errorCode; + this.errorMsg = errorMsg; + } + + public BusinessException(Integer errorCode, String errorMsg, Throwable cause) { + super(errorMsg, cause); + this.errorCode = errorCode; + this.errorMsg = errorMsg; + } + + public BusinessException(ErrorEnum error) { + super(error.getErrorMsg()); + this.errorCode = error.getErrorCode(); + this.errorMsg = error.getErrorMsg(); + } + + @Override + public String getMessage() { + return errorMsg; + } + + @Override + public synchronized Throwable fillInStackTrace() { + return this; + } +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/exception/CommonErrorEnum.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/exception/CommonErrorEnum.java new file mode 100644 index 0000000..f023de8 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/exception/CommonErrorEnum.java @@ -0,0 +1,31 @@ +package com.abin.mallchat.common.common.exception; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * Description: 通用异常码 + * Author: abin + * Date: 2023-03-26 + */ +@AllArgsConstructor +@Getter +public enum CommonErrorEnum implements ErrorEnum { + + SYSTEM_ERROR(-1, "系统出小差了,请稍后再试哦~~"), + PARAM_VALID(-2, "参数校验失败"), + FREQUENCY_LIMIT(-3, "请求太频繁了,请稍后再试哦~~"), + ; + private Integer code; + private String msg; + + @Override + public Integer getErrorCode() { + return this.code; + } + + @Override + public String getErrorMsg() { + return this.msg; + } +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/exception/ErrorEnum.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/exception/ErrorEnum.java new file mode 100644 index 0000000..6ae8b71 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/exception/ErrorEnum.java @@ -0,0 +1,8 @@ +package com.abin.mallchat.common.common.exception; + +public interface ErrorEnum { + + Integer getErrorCode(); + + String getErrorMsg(); +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/exception/GlobalExceptionHandler.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..18ee2f4 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/exception/GlobalExceptionHandler.java @@ -0,0 +1,50 @@ +package com.abin.mallchat.common.common.exception; + +import com.abin.mallchat.common.common.domain.vo.response.ApiResult; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + + +@RestControllerAdvice +@Slf4j +public class GlobalExceptionHandler { + + /** + * validation参数校验异常 + */ + @ExceptionHandler(value = MethodArgumentNotValidException.class) + public ApiResult methodArgumentNotValidExceptionExceptionHandler(MethodArgumentNotValidException e) { + StringBuilder errorMsg = new StringBuilder(); + e.getBindingResult().getFieldErrors().forEach(x -> errorMsg.append(x.getField()).append(x.getDefaultMessage()).append(",")); + String message = errorMsg.toString(); + log.info("validation parameters error!The reason is:{}", message); + return ApiResult.fail(-1, message.substring(0, message.length() - 1)); + } + /** + * 处理空指针的异常 + */ + @ExceptionHandler(value = NullPointerException.class) + public ApiResult exceptionHandler( NullPointerException e) { + log.error("null point exception!The reason is:{}", e.getMessage(), e); + return ApiResult.fail(CommonErrorEnum.SYSTEM_ERROR); + } + /** + * 未知异常 + */ + @ExceptionHandler(value = Exception.class) + public ApiResult systemExceptionHandler( Exception e) { + log.error("system exception!The reason is:{}", e.getMessage(), e); + return ApiResult.fail(CommonErrorEnum.SYSTEM_ERROR); + } + + /** + * 自定义校验异常(如参数校验等) + */ + @ExceptionHandler(value = BusinessException.class) + public ApiResult businessExceptionHandler(BusinessException e) { + log.info("business exception!The reason is:{}", e.getMessage(), e); + return ApiResult.fail(e.getErrorCode(), e.getMessage()); + } +} \ No newline at end of file diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/exception/HttpErrorEnum.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/exception/HttpErrorEnum.java new file mode 100644 index 0000000..75be440 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/exception/HttpErrorEnum.java @@ -0,0 +1,51 @@ +package com.abin.mallchat.common.common.exception; + +import cn.hutool.http.ContentType; +import cn.hutool.http.HttpUtil; +import cn.hutool.json.JSONUtil; +import com.abin.mallchat.common.common.domain.vo.response.ApiResult; +import com.google.common.base.Charsets; +import io.netty.handler.codec.http.HttpContent; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.apache.commons.lang3.CharSet; +import org.apache.commons.lang3.CharSetUtils; +import org.springframework.http.HttpStatus; +import sun.awt.CharsetString; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.nio.charset.Charset; + +/** + * Description: 业务校验异常码 + * Author: abin + * Date: 2023-03-26 + */ +@AllArgsConstructor +@Getter +public enum HttpErrorEnum implements ErrorEnum { + ACCESS_DENIED(401, "登录失效,请重新登录"), + ; + private Integer httpCode; + private String msg; + + @Override + public Integer getErrorCode() { + return httpCode; + } + + @Override + public String getErrorMsg() { + return msg; + } + + public void sendHttpError(HttpServletResponse response) throws IOException { + response.setStatus(this.getErrorCode()); + ApiResult responseData = ApiResult.fail(this); + response.setContentType(ContentType.JSON.toString(Charsets.UTF_8)); + response.getWriter().write(JSONUtil.toJsonStr(responseData)); + } +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/AssertUtil.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/AssertUtil.java new file mode 100644 index 0000000..0ffc966 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/AssertUtil.java @@ -0,0 +1,78 @@ +package com.abin.mallchat.common.common.utils; + +import cn.hutool.core.util.ObjectUtil; +import com.abin.mallchat.common.common.exception.BusinessErrorEnum; +import com.abin.mallchat.common.common.exception.BusinessException; +import com.abin.mallchat.common.common.exception.ErrorEnum; +import com.abin.mallchat.common.user.domain.entity.UserBackpack; +import org.apache.commons.lang3.StringUtils; + +import java.util.Objects; + +/** + * 校验工具类 + */ +public class AssertUtil { + + //如果不是true,则抛异常 + public static void isTrue(boolean expression, String msg) { + if (!expression) { + throwException(msg); + } + } + + public static void isTrue(boolean expression, ErrorEnum errorEnum, Object... args) { + if (!expression) { + throwException(errorEnum, args); + } + } + + //如果是true,则抛异常 + public static void isFalse(boolean expression, String msg) { + if (expression) { + throwException(msg); + } + } + + //如果是true,则抛异常 + public static void isFalse(boolean expression, ErrorEnum errorEnum, Object... args) { + if (expression) { + throwException(errorEnum, args); + } + } + + //如果不是非空对象,则抛异常 + public static void isNotEmpty(Object obj, String msg) { + if (isEmpty(obj)) { + throwException(msg); + } + } + //如果不是非空对象,则抛异常 + public static void isEmpty(Object obj, String msg) { + if (!isEmpty(obj)) { + throwException(msg); + } + } + public static void equal(Object o1,Object o2, String msg) { + if (!ObjectUtil.equal(o1,o2)) { + throwException(msg); + } + } + + private static boolean isEmpty(Object obj) { + return ObjectUtil.isEmpty(obj); + } + + private static void throwException(String msg) { + throwException(null, msg); + } + + private static void throwException(ErrorEnum errorEnum, Object... arg) { + if (Objects.isNull(errorEnum)) { + errorEnum = BusinessErrorEnum.BUSINESS_ERROR; + } + throw new BusinessException(errorEnum.getErrorCode(), String.format(errorEnum.getErrorMsg(), arg)); + } + + +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/CursorUtils.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/CursorUtils.java new file mode 100644 index 0000000..e90c803 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/CursorUtils.java @@ -0,0 +1,77 @@ +package com.abin.mallchat.common.common.utils; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.lang.Pair; +import cn.hutool.core.lang.Tuple; +import cn.hutool.core.util.StrUtil; +import com.abin.mallchat.common.chat.dao.MessageDao; +import com.abin.mallchat.common.chat.domain.entity.Message; +import com.abin.mallchat.common.common.domain.vo.request.CursorPageBaseReq; +import com.abin.mallchat.common.common.domain.vo.request.PageBaseReq; +import com.abin.mallchat.common.common.domain.vo.response.CursorPageBaseResp; +import com.abin.mallchat.common.common.domain.vo.response.PageBaseResp; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.toolkit.support.SFunction; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.google.errorprone.annotations.Var; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.ZSetOperations; +import org.springframework.stereotype.Component; +import reactor.util.function.Tuple2; + +import java.util.*; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +/** + * Description: 游标分页工具类 + * Author: abin + * Date: 2023-03-28 + */ +@Component +public class CursorUtils { + @Autowired + private RedisUtils redisUtils; + + public CursorPageBaseResp> getCursorPageByRedis(CursorPageBaseReq cursorPageBaseReq, String redisKey, Function typeConvert) { + Set> typedTuples; + if (StrUtil.isBlank(cursorPageBaseReq.getCursor())) {//第一次 + typedTuples = redisUtils.zReverseRangeWithScores(redisKey, cursorPageBaseReq.getPageSize()); + } else { + typedTuples = redisUtils.zReverseRangeByScoreWithScores(redisKey, Double.parseDouble(cursorPageBaseReq.getCursor()), cursorPageBaseReq.getPageSize()); + } + List> result = typedTuples + .stream() + .map(t -> Pair.of(typeConvert.apply(t.getValue()), t.getScore())) + .sorted((o1, o2) -> o2.getValue().compareTo(o1.getValue())) + .collect(Collectors.toList()); + String cursor = Optional.ofNullable(CollectionUtil.getLast(result)) + .map(Pair::getValue) + .map(String::valueOf) + .orElse(null); + Boolean isLast = result.size() != cursorPageBaseReq.getPageSize(); + return new CursorPageBaseResp(cursor, isLast, result); + } + + public CursorPageBaseResp getCursorPageByMysql(IService mapper, CursorPageBaseReq request, Consumer> initWrapper, SFunction cursorColumn) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + initWrapper.accept(wrapper); + if (StrUtil.isNotBlank(request.getCursor())) { + wrapper.lt(cursorColumn, request.getCursor()); + } + wrapper.orderByDesc(cursorColumn); + Page page = mapper.page(request.plusPage(), wrapper); + String cursor = Optional.ofNullable(CollectionUtil.getLast(page.getRecords())) + .map(cursorColumn) + .map(String::valueOf) + .orElse(null); + Boolean isLast = page.getRecords().size() != request.getPageSize(); + return new CursorPageBaseResp(cursor, isLast, page.getRecords()); + } + +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/JwtUtils.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/JwtUtils.java new file mode 100644 index 0000000..a7c00df --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/JwtUtils.java @@ -0,0 +1,87 @@ +package com.abin.mallchat.common.common.utils; + +import cn.hutool.json.JSONUtil; +import com.auth0.jwt.JWT; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.interfaces.Claim; +import com.auth0.jwt.interfaces.DecodedJWT; +import com.auth0.jwt.interfaces.JWTVerifier; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.util.*; + +/** + * Description: jwt的token生成与解析 + * Author: abin + * Date: 2023-04-03 + */ +@Slf4j +@Component +public class JwtUtils { + + /** + * token秘钥,请勿泄露,请勿随便修改 + */ + @Value("jwt.secret") + private String secret; + + private static final String UID_CLAIM = "uid"; + + /** + * JWT生成Token.
+ *

+ * JWT构成: header, payload, signature + */ + public String createToken(Long uid) { + // build token + String token = JWT.create() + .withClaim(UID_CLAIM, uid) // 只存一个uid信息,其他的自己去redis查 + .sign(Algorithm.HMAC256(secret)); // signature + return token; + } + + public static void main(String[] args) { + JwtUtils jwtUtils = new JwtUtils(); + String token = jwtUtils.createToken(123L); + System.out.println(token); + } + + /** + * 解密Token + * + * @param token + * @return + */ + public Map verifyToken(String token) { + if (Objects.isNull(token)) { + return null; + } + try { + JWTVerifier verifier = JWT.require(Algorithm.HMAC256(secret)).build(); + DecodedJWT jwt = verifier.verify(token); + return jwt.getClaims(); + } catch (Exception e) { + log.info("decode error,token:{}", token, e); + } + return null; + } + + + /** + * 根据Token获取uid + * + * @param token + * @return uid + */ + public Long getUidOrNull(String token) { + return Optional.ofNullable(verifyToken(token)) + .map(map -> map.get(UID_CLAIM)) + .map(Claim::asLong) + .orElse(null); + } + +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/RedisUtils.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/RedisUtils.java new file mode 100644 index 0000000..5124b44 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/RedisUtils.java @@ -0,0 +1,1081 @@ +package com.abin.mallchat.common.common.utils; + +import cn.hutool.json.JSONUtil; +import com.fasterxml.jackson.databind.ObjectMapper; +import net.sf.json.util.JSONUtils; +import org.springframework.data.redis.core.ZSetOperations.TypedTuple; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.*; +import org.springframework.data.redis.core.script.DefaultRedisScript; +import org.springframework.data.redis.core.script.RedisScript; +import org.springframework.stereotype.Component; + +import java.security.Key; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + + +@Slf4j +@Component +public class RedisUtils { + private static final ObjectMapper jsonMapper = new ObjectMapper(); + public RedisTemplate redisTemplate; + + public StringRedisTemplate stringRedisTemplate; + + public RedisUtils(RedisTemplate redisTemplate, StringRedisTemplate stringRedisTemplate) { + this.redisTemplate = redisTemplate; + this.stringRedisTemplate = stringRedisTemplate; + } + + private static final String LUA_INCR_EXPIRE = + "local key,ttl=KEYS[1],ARGV[1] \n" + + " \n" + + "if redis.call('EXISTS',key)==0 then \n" + + " redis.call('SETEX',key,ttl,1) \n" + + " return 1 \n" + + "else \n" + + " return tonumber(redis.call('INCR',key)) \n" + + "end "; + + public Long inc(String key, int time, TimeUnit unit) { + RedisScript redisScript = new DefaultRedisScript<>(LUA_INCR_EXPIRE, Long.class); + return (Long) redisTemplate.execute(redisScript, Collections.singletonList(key), unit.toSeconds(time)); + } + + /** + * 指定缓存失效时间 + * + * @param key 键 + * @param time 时间(秒) + */ + public boolean expire(String key, long time) { + try { + if (time > 0) { + redisTemplate.expire(key, time, TimeUnit.SECONDS); + } + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + return true; + } + + /** + * 指定缓存失效时间 + * + * @param key 键 + * @param time 时间(秒) + * @param timeUnit 单位 + */ + public boolean expire(String key, long time, TimeUnit timeUnit) { + try { + if (time > 0) { + redisTemplate.expire(key, time, timeUnit); + } + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + return true; + } + + /** + * 根据 key 获取过期时间 + * + * @param key 键 不能为null + * @return 时间(秒) 返回0代表为永久有效 + */ + public long getExpire(Object key) { + return redisTemplate.getExpire(key, TimeUnit.SECONDS); + } + + /** + * 根据 key 获取过期时间 + * + * @param key 键 不能为null + * @return 时间(秒) 返回0代表为永久有效 + */ + public long getExpire(Object key, TimeUnit timeUnit) { + return redisTemplate.getExpire(key, timeUnit); + } + + /** + * 查找匹配key + * + * @param pattern key + * @return / + */ + public List scan(String pattern) { + ScanOptions options = ScanOptions.scanOptions().match(pattern).build(); + RedisConnectionFactory factory = redisTemplate.getConnectionFactory(); + RedisConnection rc = Objects.requireNonNull(factory).getConnection(); + Cursor cursor = rc.scan(options); + List result = new ArrayList<>(); + while (cursor.hasNext()) { + result.add(new String(cursor.next())); + } + try { + RedisConnectionUtils.releaseConnection(rc, factory); + } catch (Exception e) { + log.error(e.getMessage(), e); + } + return result; + } + + /** + * 分页查询 key + * + * @param patternKey key + * @param page 页码 + * @param size 每页数目 + * @return / + */ + public List findKeysForPage(String patternKey, int page, int size) { + ScanOptions options = ScanOptions.scanOptions().match(patternKey).build(); + RedisConnectionFactory factory = redisTemplate.getConnectionFactory(); + RedisConnection rc = Objects.requireNonNull(factory).getConnection(); + Cursor cursor = rc.scan(options); + List result = new ArrayList<>(size); + int tmpIndex = 0; + int fromIndex = page * size; + int toIndex = page * size + size; + while (cursor.hasNext()) { + if (tmpIndex >= fromIndex && tmpIndex < toIndex) { + result.add(new String(cursor.next())); + tmpIndex++; + continue; + } + // 获取到满足条件的数据后,就可以退出了 + if (tmpIndex >= toIndex) { + break; + } + tmpIndex++; + cursor.next(); + } + try { + RedisConnectionUtils.releaseConnection(rc, factory); + } catch (Exception e) { + log.error(e.getMessage(), e); + } + return result; + } + + /** + * 判断key是否存在 + * + * @param key 键 + * @return true 存在 false不存在 + */ + public boolean hasKey(String key) { + try { + return redisTemplate.hasKey(key); + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + + /** + * 删除缓存 + * + * @param keys + */ + public void del(String... keys) { + if (keys != null && keys.length > 0) { + if (keys.length == 1) { + boolean result = redisTemplate.delete(keys[0]); + log.debug("--------------------------------------------"); + log.debug(new StringBuilder("删除缓存:").append(keys[0]).append(",结果:").append(result).toString()); + log.debug("--------------------------------------------"); + } else { + Set keySet = new HashSet<>(); + for (String key : keys) { + keySet.addAll(redisTemplate.keys(key)); + } + long count = redisTemplate.delete(keySet); + log.debug("--------------------------------------------"); + log.debug("成功删除缓存:" + keySet.toString()); + log.debug("缓存删除数量:" + count + "个"); + log.debug("--------------------------------------------"); + } + } + } + + // ============================String============================= + + /** + * 普通缓存获取 + * + * @param key 键 + * @return 值 + */ + public Object get(String key) { + return key == null ? null : redisTemplate.opsForValue().get(key); + } + + public String getStr(String key) { + return stringRedisTemplate.opsForValue().get(key); + } + + public T get(String key, Class tClass) { + Object o = get(key); + return toBeanOrNull(o, tClass); + } + + public List mget(Collection keys, Class tClass) { + List list = redisTemplate.opsForValue().multiGet(keys); + return (List) list.stream().map(o -> toBeanOrNull(o, tClass)).collect(Collectors.toList()); + } + + private T toBeanOrNull(Object o, Class tClass) { + return o == null ? null : toObj(o.toString(),tClass); + } + public static T toObj(String str, Class clz) { + try { + return jsonMapper.readValue(str, clz); + } catch (Exception e) { + throw new UnsupportedOperationException(e); + } + } + + public void mset(Map map, long time) { + redisTemplate.opsForValue().multiSet(map); + map.forEach((key, value) -> { + expire(key, time); + }); + } + + /** + * 普通缓存放入 + * + * @param key 键 + * @param value 值 + * @return true成功 false失败 + */ + public boolean set(String key, Object value) { + try { + redisTemplate.opsForValue().set(key, value); + return true; + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + /** + * 普通缓存放入并设置时间 + * + * @param key 键 + * @param value 值 + * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期 + * @return true成功 false 失败 + */ + public boolean set(String key, Object value, long time) { + try { + if (time > 0) { + redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS); + } else { + set(key, value); + } + return true; + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + /** + * 普通缓存放入并设置时间 + * + * @param key 键 + * @param value 值 + * @param time 时间 + * @param timeUnit 类型 + * @return true成功 false 失败 + */ + public boolean set(String key, Object value, long time, TimeUnit timeUnit) { + try { + if (time > 0) { + redisTemplate.opsForValue().set(key, value, time, timeUnit); + } else { + set(key, value); + } + return true; + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + // ================================Map================================= + + /** + * HashGet + * + * @param key 键 不能为null + * @param item 项 不能为null + * @return 值 + */ + public Object hget(String key, String item) { + return redisTemplate.opsForHash().get(key, item); + } + + /** + * 获取hashKey对应的所有键值 + * + * @param key 键 + * @return 对应的多个键值 + */ + public Map hmget(String key) { + return redisTemplate.opsForHash().entries(key); + + } + + /** + * HashSet + * + * @param key 键 + * @param map 对应多个键值 + * @return true 成功 false 失败 + */ + public boolean hmset(String key, Map map) { + try { + redisTemplate.opsForHash().putAll(key, map); + return true; + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + /** + * HashSet 并设置时间 + * + * @param key 键 + * @param map 对应多个键值 + * @param time 时间(秒) + * @return true成功 false失败 + */ + public boolean hmset(String key, Map map, long time) { + try { + redisTemplate.opsForHash().putAll(key, map); + if (time > 0) { + expire(key, time); + } + return true; + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + /** + * 向一张hash表中放入数据,如果不存在将创建 + * + * @param key 键 + * @param item 项 + * @param value 值 + * @return true 成功 false失败 + */ + public boolean hset(String key, String item, Object value) { + try { + redisTemplate.opsForHash().put(key, item, value); + return true; + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + /** + * 向一张hash表中放入数据,如果不存在将创建 + * + * @param key 键 + * @param item 项 + * @param value 值 + * @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间 + * @return true 成功 false失败 + */ + public boolean hset(String key, String item, Object value, long time) { + try { + redisTemplate.opsForHash().put(key, item, value); + if (time > 0) { + expire(key, time); + } + return true; + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + /** + * 删除hash表中的值 + * + * @param key 键 不能为null + * @param item 项 可以使多个 不能为null + */ + public void hdel(String key, Object... item) { + redisTemplate.opsForHash().delete(key, item); + } + + /** + * 判断hash表中是否有该项的值 + * + * @param key 键 不能为null + * @param item 项 不能为null + * @return true 存在 false不存在 + */ + public boolean hHasKey(String key, String item) { + return redisTemplate.opsForHash().hasKey(key, item); + } + + /** + * hash递增 如果不存在,就会创建一个 并把新增后的值返回 + * + * @param key 键 + * @param item 项 + * @param by 要增加几(大于0) + * @return + */ + public double hincr(String key, String item, double by) { + return redisTemplate.opsForHash().increment(key, item, by); + } + + /** + * hash递减 + * + * @param key 键 + * @param item 项 + * @param by 要减少记(小于0) + * @return + */ + public double hdecr(String key, String item, double by) { + return redisTemplate.opsForHash().increment(key, item, -by); + } + + // ============================set============================= + + /** + * 根据key获取Set中的所有值 + * + * @param key 键 + * @return + */ + public Set sGet(String key) { + try { + return redisTemplate.opsForSet().members(key); + } catch (Exception e) { + log.error(e.getMessage(), e); + return null; + } + } + + /** + * 根据value从一个set中查询,是否存在 + * + * @param key 键 + * @param value 值 + * @return true 存在 false不存在 + */ + public boolean sHasKey(String key, Object value) { + try { + return redisTemplate.opsForSet().isMember(key, value); + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + /** + * 将数据放入set缓存 + * + * @param key 键 + * @param values 值 可以是多个 + * @return 成功个数 + */ + public long sSet(String key, Object... values) { + try { + return redisTemplate.opsForSet().add(key, values); + } catch (Exception e) { + log.error(e.getMessage(), e); + return 0; + } + } + + /** + * 将set数据放入缓存 + * + * @param key 键 + * @param time 时间(秒) + * @param values 值 可以是多个 + * @return 成功个数 + */ + public long sSetAndTime(String key, long time, Object... values) { + try { + Long count = redisTemplate.opsForSet().add(key, values); + if (time > 0) { + expire(key, time); + } + return count; + } catch (Exception e) { + log.error(e.getMessage(), e); + return 0; + } + } + + /** + * 获取set缓存的长度 + * + * @param key 键 + * @return + */ + public long sGetSetSize(String key) { + try { + return redisTemplate.opsForSet().size(key); + } catch (Exception e) { + log.error(e.getMessage(), e); + return 0; + } + } + + /** + * 移除值为value的 + * + * @param key 键 + * @param values 值 可以是多个 + * @return 移除的个数 + */ + public long setRemove(String key, Object... values) { + try { + Long count = redisTemplate.opsForSet().remove(key, values); + return count; + } catch (Exception e) { + log.error(e.getMessage(), e); + return 0; + } + } + + // ===============================list================================= + + /** + * 获取list缓存的内容 + * + * @param key 键 + * @param start 开始 + * @param end 结束 0 到 -1代表所有值 + * @return + */ + public List lGet(String key, long start, long end) { + try { + return redisTemplate.opsForList().range(key, start, end); + } catch (Exception e) { + log.error(e.getMessage(), e); + return null; + } + } + + /** + * 获取list缓存的长度 + * + * @param key 键 + * @return + */ + public long lGetListSize(String key) { + try { + return redisTemplate.opsForList().size(key); + } catch (Exception e) { + log.error(e.getMessage(), e); + return 0; + } + } + + /** + * 通过索引 获取list中的值 + * + * @param key 键 + * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推 + * @return + */ + public Object lGetIndex(String key, long index) { + try { + return redisTemplate.opsForList().index(key, index); + } catch (Exception e) { + log.error(e.getMessage(), e); + return null; + } + } + + /** + * 将list放入缓存 + * + * @param key 键 + * @param value 值 + * @return + */ + public boolean lSet(String key, Object value) { + try { + redisTemplate.opsForList().rightPush(key, value); + return true; + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + /** + * 将list放入缓存 + * + * @param key 键 + * @param value 值 + * @param time 时间(秒) + * @return + */ + public boolean lSet(String key, Object value, long time) { + try { + redisTemplate.opsForList().rightPush(key, value); + if (time > 0) { + expire(key, time); + } + return true; + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + /** + * 将list放入缓存 + * + * @param key 键 + * @param value 值 + * @return + */ + public boolean lSet(String key, List value) { + try { + redisTemplate.opsForList().rightPushAll(key, value); + return true; + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + /** + * 将list放入缓存 + * + * @param key 键 + * @param value 值 + * @param time 时间(秒) + * @return + */ + public boolean lSet(String key, List value, long time) { + try { + redisTemplate.opsForList().rightPushAll(key, value); + if (time > 0) { + expire(key, time); + } + return true; + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + /** + * 根据索引修改list中的某条数据 + * + * @param key 键 + * @param index 索引 + * @param value 值 + * @return / + */ + public boolean lUpdateIndex(String key, long index, Object value) { + try { + redisTemplate.opsForList().set(key, index, value); + return true; + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + /** + * 移除N个值为value + * + * @param key 键 + * @param count 移除多少个 + * @param value 值 + * @return 移除的个数 + */ + public long lRemove(String key, long count, Object value) { + try { + return redisTemplate.opsForList().remove(key, count, value); + } catch (Exception e) { + log.error(e.getMessage(), e); + return 0; + } + } + + /** + * @param prefix 前缀 + * @param ids id + */ + public void delByKeys(String prefix, Set ids) { + Set keys = new HashSet<>(); + for (Long id : ids) { + keys.addAll(redisTemplate.keys(new StringBuffer(prefix).append(id).toString())); + } + long count = redisTemplate.delete(keys); + // 此处提示可自行删除 + log.debug("--------------------------------------------"); + log.debug("成功删除缓存:" + keys.toString()); + log.debug("缓存删除数量:" + count + "个"); + log.debug("--------------------------------------------"); + } + /**------------------zSet相关操作--------------------------------*/ + + /** + * 添加元素,有序集合是按照元素的score值由小到大排列 + * + * @param key + * @param value + * @param score + * @return + */ + public Boolean zAdd(String key, String value, double score) { + return redisTemplate.opsForZSet().add(key, value, score); + } + + public Boolean zAdd(String key, Object value, double score) { + return zAdd(key, value.toString(), score); + } + + /** + * @param key + * @param values + * @return + */ + public Long zAdd(String key, Set> values) { + return redisTemplate.opsForZSet().add(key, values); + } + + /** + * @param key + * @param values + * @return + */ + public Long zRemove(String key, Object... values) { + return redisTemplate.opsForZSet().remove(key, values); + } + + public Long zRemove(String key, Object value) { + return zRemove(key, value.toString()); + } + + public Long zRemove(String key, String value) { + return redisTemplate.opsForZSet().remove(key, value); + } + + /** + * 增加元素的score值,并返回增加后的值 + * + * @param key + * @param value + * @param delta + * @return + */ + public Double zIncrementScore(String key, String value, double delta) { + return redisTemplate.opsForZSet().incrementScore(key, value, delta); + } + + /** + * 返回元素在集合的排名,有序集合是按照元素的score值由小到大排列 + * + * @param key + * @param value + * @return 0表示第一位 + */ + public Long zRank(String key, Object value) { + return redisTemplate.opsForZSet().rank(key, value); + } + + /** + * 返回元素在集合的排名,按元素的score值由大到小排列 + * + * @param key + * @param value + * @return + */ + public Long zReverseRank(String key, Object value) { + return redisTemplate.opsForZSet().reverseRank(key, value); + } + + /** + * 获取集合的元素, 从小到大排序 + * + * @param key + * @param start 开始位置 + * @param end 结束位置, -1查询所有 + * @return + */ + public Set zRange(String key, long start, long end) { + return redisTemplate.opsForZSet().range(key, start, end); + } + + /** + * 获取集合元素, 并且把score值也获取 + * + * @param key + * @param start + * @param end + * @return + */ + public Set> zRangeWithScores(String key, long start, + long end) { + return redisTemplate.opsForZSet().rangeWithScores(key, start, end); + } + + /** + * 根据Score值查询集合元素 + * + * @param key + * @param min 最小值 + * @param max 最大值 + * @return + */ + public Set zRangeByScore(String key, double min, double max) { + return redisTemplate.opsForZSet().rangeByScore(key, min, max); + } + + /** + * 根据Score值查询集合元素, 从小到大排序 + * + * @param key + * @param min 最小值 + * @param max 最大值 + * @return + */ + public Set> zRangeByScoreWithScores(String key, + double min, double max) { + return redisTemplate.opsForZSet().rangeByScoreWithScores(key, min, max); + } + + /** + * @param key + * @param min + * @param max + * @param start + * @param end + * @return + */ + public Set> zRangeByScoreWithScores(String key, + double min, double max, long start, long end) { + return redisTemplate.opsForZSet().rangeByScoreWithScores(key, min, max, + start, end); + } + + /** + * 获取集合的元素, 从大到小排序 + * + * @param key + * @param start + * @param end + * @return + */ + public Set zReverseRange(String key, long start, long end) { + return redisTemplate.opsForZSet().reverseRange(key, start, end); + } + +// /** +// * 获取集合的元素, 从大到小排序, 并返回score值 +// * +// * @param key +// * @param start +// * @param end +// * @return +// */ +// public Set> zReverseRangeWithScores(String key, +// long start, long end) { +// return redisTemplate.opsForZSet().reverseRangeWithScores(key, start, +// end); +// } + + /** + * 获取集合的元素, 从大到小排序, 并返回score值 + * + * @param key + * @param pageSize + * @return + */ + public Set> zReverseRangeWithScores(String key, + long pageSize) { + return redisTemplate.opsForZSet().reverseRangeByScoreWithScores(key, Double.MIN_VALUE, + Double.MAX_VALUE, 0, pageSize); + } + + /** + * @param key + * @param max + * @param pageSize + * @return + */ + public Set> zReverseRangeByScoreWithScores(String key, + double max, long pageSize) { + return redisTemplate.opsForZSet().reverseRangeByScoreWithScores(key, Double.MIN_VALUE, max, + 1, pageSize); + } + +// /** +// * 根据Score值查询集合元素, 从大到小排序 +// * +// * @param key +// * @param min +// * @param max +// * @return +// */ +// public Set zReverseRangeByScore(String key, double min, +// double max) { +// return redisTemplate.opsForZSet().reverseRangeByScore(key, min, max); +// } + +// /** +// * 根据Score值查询集合元素, 从大到小排序 +// * +// * @param key +// * @param min +// * @param max +// * @return +// */ +// public Set> zReverseRangeByScoreWithScores( +// String key, double min, double max) { +// return redisTemplate.opsForZSet().reverseRangeByScoreWithScores(key, +// min, max); +// } + + + /** + * 根据score值获取集合元素数量 + * + * @param key + * @param min + * @param max + * @return + */ + public Long zCount(String key, double min, double max) { + return redisTemplate.opsForZSet().count(key, min, max); + } + + /** + * 获取集合大小 + * + * @param key + * @return + */ + public Long zSize(String key) { + return redisTemplate.opsForZSet().size(key); + } + + /** + * 获取集合大小 + * + * @param key + * @return + */ + public Long zCard(String key) { + return redisTemplate.opsForZSet().zCard(key); + } + + /** + * 获取集合中value元素的score值 + * + * @param key + * @param value + * @return + */ + public Double zScore(String key, Object value) { + return redisTemplate.opsForZSet().score(key, value); + } + + /** + * 移除指定索引位置的成员 + * + * @param key + * @param start + * @param end + * @return + */ + public Long zRemoveRange(String key, long start, long end) { + return redisTemplate.opsForZSet().removeRange(key, start, end); + } + + /** + * 根据指定的score值的范围来移除成员 + * + * @param key + * @param min + * @param max + * @return + */ + public Long zRemoveRangeByScore(String key, double min, double max) { + return redisTemplate.opsForZSet().removeRangeByScore(key, min, max); + } + + /** + * 获取key和otherKey的并集并存储在destKey中 + * + * @param key + * @param otherKey + * @param destKey + * @return + */ + public Long zUnionAndStore(String key, String otherKey, String destKey) { + return redisTemplate.opsForZSet().unionAndStore(key, otherKey, destKey); + } + + /** + * @param key + * @param otherKeys + * @param destKey + * @return + */ + public Long zUnionAndStore(String key, Collection otherKeys, + String destKey) { + return redisTemplate.opsForZSet() + .unionAndStore(key, otherKeys, destKey); + } + + /** + * 交集 + * + * @param key + * @param otherKey + * @param destKey + * @return + */ + public Long zIntersectAndStore(String key, String otherKey, + String destKey) { + return redisTemplate.opsForZSet().intersectAndStore(key, otherKey, + destKey); + } + + /** + * 交集 + * + * @param key + * @param otherKeys + * @param destKey + * @return + */ + public Long zIntersectAndStore(String key, Collection otherKeys, + String destKey) { + return redisTemplate.opsForZSet().intersectAndStore(key, otherKeys, + destKey); + } + +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/RequestHolder.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/RequestHolder.java new file mode 100644 index 0000000..3544ab2 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/RequestHolder.java @@ -0,0 +1,25 @@ +package com.abin.mallchat.common.common.utils; + +import com.abin.mallchat.common.common.domain.dto.RequestInfo; + +/** + * Description: 请求上下文 + * Author: abin + * Date: 2023-04-05 + */ +public class RequestHolder { + + private static final ThreadLocal threadLocal = new ThreadLocal<>(); + + public static void set(RequestInfo requestInfo) { + threadLocal.set(requestInfo); + } + + public static RequestInfo get() { + return threadLocal.get(); + } + + public static void remove() { + threadLocal.remove(); + } +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/user/dao/ItemConfigDao.java b/mallchat-common/src/main/java/com/abin/mallchat/common/user/dao/ItemConfigDao.java new file mode 100644 index 0000000..9c9fcce --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/user/dao/ItemConfigDao.java @@ -0,0 +1,26 @@ +package com.abin.mallchat.common.user.dao; + +import com.abin.mallchat.common.user.domain.entity.ItemConfig; +import com.abin.mallchat.common.user.mapper.ItemConfigMapper; +import com.abin.mallchat.common.user.service.IItemConfigService; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + *

+ * 功能物品配置表 服务实现类 + *

+ * + * @author abin + * @since 2023-03-19 + */ +@Service +public class ItemConfigDao extends ServiceImpl { + + public List getByType(Integer type) { + return lambdaQuery().eq(ItemConfig::getType, type).list(); + } +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/user/dao/UserBackpackDao.java b/mallchat-common/src/main/java/com/abin/mallchat/common/user/dao/UserBackpackDao.java new file mode 100644 index 0000000..5e57ab0 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/user/dao/UserBackpackDao.java @@ -0,0 +1,60 @@ +package com.abin.mallchat.common.user.dao; + +import com.abin.mallchat.common.common.domain.enums.YesOrNoEnum; +import com.abin.mallchat.common.user.domain.entity.User; +import com.abin.mallchat.common.user.domain.entity.UserBackpack; +import com.abin.mallchat.common.user.mapper.UserBackpackMapper; +import com.abin.mallchat.common.user.service.IUserBackpackService; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + *

+ * 用户背包表 服务实现类 + *

+ * + * @author abin + * @since 2023-03-19 + */ +@Service +public class UserBackpackDao extends ServiceImpl { + + public Integer getCountByValidItemId(Long uid, Long itemId) { + LambdaQueryWrapper wrapper = new QueryWrapper().lambda() + .eq(UserBackpack::getUid, uid) + .eq(UserBackpack::getItemId, itemId) + .eq(UserBackpack::getStatus, YesOrNoEnum.NO.getStatus()); + return count(wrapper); + } + + public UserBackpack getFirstValidItem(Long uid, Long itemId) { + LambdaQueryWrapper wrapper = new QueryWrapper().lambda() + .eq(UserBackpack::getUid, uid) + .eq(UserBackpack::getItemId, itemId) + .eq(UserBackpack::getStatus, YesOrNoEnum.NO.getStatus()) + .last("limit 1"); + return getOne(wrapper); + } + + public boolean invalidItem(Long itemId) { + UserBackpack update = new UserBackpack(); + update.setItemId(itemId); + update.setStatus(YesOrNoEnum.YES.getStatus()); + return updateById(update); + } + + public List getByItemIds(Long uid, List itemIds) { + return lambdaQuery().eq(UserBackpack::getUid, uid) + .in(UserBackpack::getItemId, itemIds) + .eq(UserBackpack::getStatus, YesOrNoEnum.NO.getStatus()) + .list(); + } + + public UserBackpack getByIdp(String idempotent) { + return lambdaQuery().eq(UserBackpack::getIdempotent, idempotent).one(); + } +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/user/dao/UserDao.java b/mallchat-common/src/main/java/com/abin/mallchat/common/user/dao/UserDao.java new file mode 100644 index 0000000..2aa2efc --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/user/dao/UserDao.java @@ -0,0 +1,40 @@ +package com.abin.mallchat.common.user.dao; + +import com.abin.mallchat.common.user.domain.entity.User; +import com.abin.mallchat.common.user.mapper.UserMapper; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +/** + *

+ * 用户表 服务实现类 + *

+ * + * @author abin + * @since 2023-03-19 + */ +@Service +public class UserDao extends ServiceImpl { + + public User getByOpenId(String openId) { + LambdaQueryWrapper wrapper = new QueryWrapper().lambda().eq(User::getOpenId, openId); + return getOne(wrapper); + } + + public void modifyName(Long uid, String name) { + User update = new User(); + update.setId(uid); + update.setName(name); + updateById(update); + } + + public void wearingBadge(Long uid, Long badgeId) { + User update = new User(); + update.setId(uid); + update.setItemId(badgeId); + updateById(update); + } +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/user/domain/dto/IpResult.java b/mallchat-common/src/main/java/com/abin/mallchat/common/user/domain/dto/IpResult.java new file mode 100644 index 0000000..f3842b5 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/user/domain/dto/IpResult.java @@ -0,0 +1,26 @@ +package com.abin.mallchat.common.user.domain.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; +import java.util.Objects; + +/** + * Description: + * Author: abin + * Date: 2023-04-18 + */ +@Data +public class IpResult implements Serializable { + @ApiModelProperty("错误码") + private Integer code; + @ApiModelProperty("错误消息") + private String msg; + @ApiModelProperty("返回对象") + private T data; + + public boolean isSuccess() { + return Objects.nonNull(this.code) && this.code == 0; + } +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/user/domain/entity/IpDetail.java b/mallchat-common/src/main/java/com/abin/mallchat/common/user/domain/entity/IpDetail.java new file mode 100644 index 0000000..323c568 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/user/domain/entity/IpDetail.java @@ -0,0 +1,33 @@ +package com.abin.mallchat.common.user.domain.entity; + +import lombok.*; + +import java.io.Serializable; + +/** + *

+ * 用户ip信息 + *

+ * + * @author abin + * @since 2023-03-19 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class IpDetail implements Serializable { + + private static final long serialVersionUID = 1L; + //注册时的ip + private String ip; + //最新登录的ip + private String isp; + private String isp_id; + private String city; + private String city_id; + private String country; + private String country_id; + private String region; + private String region_id; +} \ No newline at end of file diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/user/domain/entity/IpInfo.java b/mallchat-common/src/main/java/com/abin/mallchat/common/user/domain/entity/IpInfo.java new file mode 100644 index 0000000..201ea7a --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/user/domain/entity/IpInfo.java @@ -0,0 +1,63 @@ +package com.abin.mallchat.common.user.domain.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.io.Serializable; +import java.util.Objects; +import java.util.Optional; + +/** + *

+ * 用户ip信息 + *

+ * + * @author abin + * @since 2023-03-19 + */ +@Data +@EqualsAndHashCode(callSuper = false) +public class IpInfo implements Serializable { + + private static final long serialVersionUID = 1L; + //注册时的ip + private String createIp; + //注册时的ip详情 + private IpDetail createIpDetail; + //最新登录的ip + private String updateIp; + //最新登录的ip详情 + private IpDetail updateIpDetail; + + public void refreshIp(String ip) { + if (Objects.isNull(ip)) { + return; + } + updateIp = ip; + if (createIp == null) { + createIp = ip; + } + } + + /** + * 需要刷新的ip,这里判断更新ip就够,初始化的时候ip也是相同的,只需要设置的时候多设置进去就行 + * + * @return + */ + public String needRefreshIp() { + boolean notNeedRefresh = Optional.ofNullable(updateIpDetail) + .map(IpDetail::getIp) + .filter(ip -> ip.equals(updateIp)) + .isPresent(); + return notNeedRefresh ? null : updateIp; + } + + public void refreshIpDetail(IpDetail ipDetail) { + if (Objects.equals(createIp, ipDetail)) { + createIpDetail = ipDetail; + } + if (Objects.equals(updateIp, ipDetail)) { + updateIpDetail = ipDetail; + } + } +} \ No newline at end of file diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/user/domain/entity/ItemConfig.java b/mallchat-common/src/main/java/com/abin/mallchat/common/user/domain/entity/ItemConfig.java new file mode 100644 index 0000000..55b0127 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/user/domain/entity/ItemConfig.java @@ -0,0 +1,63 @@ +package com.abin.mallchat.common.user.domain.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.baomidou.mybatisplus.annotation.TableField; +import java.io.Serializable; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + *

+ * 功能物品配置表 + *

+ * + * @author abin + * @since 2023-03-19 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("item_config") +public class ItemConfig implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * id + */ + @TableId("id") + private Long id; + + /** + * 物品类型 1改名卡 2徽章 + */ + @TableField("type") + private Integer type; + + /** + * 物品图片 + */ + @TableField("img") + private String img; + + /** + * 物品功能描述 + */ + @TableField("`describe`") + private String describe; + + /** + * 创建时间 + */ + @TableField("create_time") + private LocalDateTime createTime; + + /** + * 修改时间 + */ + @TableField("update_time") + private LocalDateTime updateTime; + + +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/user/domain/entity/User.java b/mallchat-common/src/main/java/com/abin/mallchat/common/user/domain/entity/User.java new file mode 100644 index 0000000..a897446 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/user/domain/entity/User.java @@ -0,0 +1,103 @@ +package com.abin.mallchat.common.user.domain.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; + +import java.time.LocalDateTime; + +import com.baomidou.mybatisplus.annotation.TableField; + +import java.io.Serializable; +import java.util.Date; + +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import lombok.*; +import lombok.experimental.Accessors; + +/** + *

+ * 用户表 + *

+ * + * @author abin + * @since 2023-03-19 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Builder +@AllArgsConstructor +@NoArgsConstructor +@TableName("user") +public class User implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 用户id + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 用户昵称 + */ + @TableField("name") + private String name; + + /** + * 用户头像 + */ + @TableField("avatar") + private String avatar; + + /** + * 性别 1为男性,2为女性 + */ + @TableField("sex") + private Integer sex; + + /** + * 微信openid用户标识 + */ + @TableField("open_id") + private String openId; + + /** + * 最后上下线时间 + */ + @TableField("last_opt_time") + private Date lastOptTime; + + /** + * 最后上下线时间 + */ + @TableField(value = "ip_info", typeHandler = JacksonTypeHandler.class) + private IpInfo ipInfo; + + /** + * 佩戴的徽章id + */ + @TableField("item_id") + private Long itemId; + + /** + * 创建时间 + */ + @TableField("create_time") + private Date createTime; + + /** + * 修改时间 + */ + @TableField("update_time") + private Date updateTime; + + public IpInfo getIpInfo() { + if (ipInfo == null) { + ipInfo = new IpInfo(); + } + return ipInfo; + } + +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/user/domain/entity/UserBackpack.java b/mallchat-common/src/main/java/com/abin/mallchat/common/user/domain/entity/UserBackpack.java new file mode 100644 index 0000000..48f88f9 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/user/domain/entity/UserBackpack.java @@ -0,0 +1,74 @@ +package com.abin.mallchat.common.user.domain.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.baomidou.mybatisplus.annotation.TableField; +import java.io.Serializable; + +import lombok.*; +import lombok.experimental.Accessors; + +/** + *

+ * 用户背包表 + *

+ * + * @author abin + * @since 2023-03-19 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Builder +@AllArgsConstructor +@NoArgsConstructor +@TableName("user_backpack") +public class UserBackpack implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * id + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * uid + */ + @TableField("uid") + private Long uid; + + /** + * 物品id + */ + @TableField("item_id") + private Long itemId; + + /** + * 使用状态 0.未失效 1失效 + */ + @TableField("status") + private Integer status; + + /** + * 幂等号 + */ + @TableField("idempotent") + private String idempotent; + + /** + * 创建时间 + */ + @TableField("create_time") + private LocalDateTime createTime; + + /** + * 修改时间 + */ + @TableField("update_time") + private LocalDateTime updateTime; + + +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/user/domain/enums/ChatActiveStatusEnum.java b/mallchat-common/src/main/java/com/abin/mallchat/common/user/domain/enums/ChatActiveStatusEnum.java new file mode 100644 index 0000000..f4d0736 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/user/domain/enums/ChatActiveStatusEnum.java @@ -0,0 +1,35 @@ +package com.abin.mallchat.common.user.domain.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * Description: ws前端请求类型枚举 + * Author: abin + * Date: 2023-03-19 + */ +@AllArgsConstructor +@Getter +public enum ChatActiveStatusEnum { + ONLINE(1, "在线"), + OFFLINE(2, "离线"), + ; + + private final Integer status; + private final String desc; + + private static Map cache; + + static { + cache = Arrays.stream(ChatActiveStatusEnum.values()).collect(Collectors.toMap(ChatActiveStatusEnum::getStatus, Function.identity())); + } + + public static ChatActiveStatusEnum of(Integer type) { + return cache.get(type); + } +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/user/domain/enums/ItemEnum.java b/mallchat-common/src/main/java/com/abin/mallchat/common/user/domain/enums/ItemEnum.java new file mode 100644 index 0000000..8a917a2 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/user/domain/enums/ItemEnum.java @@ -0,0 +1,38 @@ +package com.abin.mallchat.common.user.domain.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * Description: 物品枚举 + * Author: abin + * Date: 2023-03-19 + */ +@AllArgsConstructor +@Getter +public enum ItemEnum { + MODIFY_NAME_CARD(1L, ItemTypeEnum.MODIFY_NAME_CARD, "改名卡"), + LIKE_BADGE(2L, ItemTypeEnum.BADGE, "爆赞徽章"), + REG_TOP10_BADGE(2L, ItemTypeEnum.BADGE, "爆赞徽章"), + REG_TOP100_BADGE(2L, ItemTypeEnum.BADGE, "爆赞徽章"), + ; + + private final Long id; + private final ItemTypeEnum typeEnum; + private final String desc; + + private static Map cache; + + static { + cache = Arrays.stream(ItemEnum.values()).collect(Collectors.toMap(ItemEnum::getId, Function.identity())); + } + + public static ItemEnum of(Integer type) { + return cache.get(type); + } +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/user/domain/enums/ItemTypeEnum.java b/mallchat-common/src/main/java/com/abin/mallchat/common/user/domain/enums/ItemTypeEnum.java new file mode 100644 index 0000000..33ab805 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/user/domain/enums/ItemTypeEnum.java @@ -0,0 +1,35 @@ +package com.abin.mallchat.common.user.domain.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * Description: 物品枚举 + * Author: abin + * Date: 2023-03-19 + */ +@AllArgsConstructor +@Getter +public enum ItemTypeEnum { + MODIFY_NAME_CARD(1, "改名卡"), + BADGE(2, "徽章"), + ; + + private final Integer type; + private final String desc; + + private static Map cache; + + static { + cache = Arrays.stream(ItemTypeEnum.values()).collect(Collectors.toMap(ItemTypeEnum::getType, Function.identity())); + } + + public static ItemTypeEnum of(Integer type) { + return cache.get(type); + } +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/user/mapper/ItemConfigMapper.java b/mallchat-common/src/main/java/com/abin/mallchat/common/user/mapper/ItemConfigMapper.java new file mode 100644 index 0000000..098c6a2 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/user/mapper/ItemConfigMapper.java @@ -0,0 +1,16 @@ +package com.abin.mallchat.common.user.mapper; + +import com.abin.mallchat.common.user.domain.entity.ItemConfig; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * 功能物品配置表 Mapper 接口 + *

+ * + * @author abin + * @since 2023-03-19 + */ +public interface ItemConfigMapper extends BaseMapper { + +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/user/mapper/UserBackpackMapper.java b/mallchat-common/src/main/java/com/abin/mallchat/common/user/mapper/UserBackpackMapper.java new file mode 100644 index 0000000..e71ff44 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/user/mapper/UserBackpackMapper.java @@ -0,0 +1,16 @@ +package com.abin.mallchat.common.user.mapper; + +import com.abin.mallchat.common.user.domain.entity.UserBackpack; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * 用户背包表 Mapper 接口 + *

+ * + * @author abin + * @since 2023-03-19 + */ +public interface UserBackpackMapper extends BaseMapper { + +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/user/mapper/UserMapper.java b/mallchat-common/src/main/java/com/abin/mallchat/common/user/mapper/UserMapper.java new file mode 100644 index 0000000..08aec0d --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/user/mapper/UserMapper.java @@ -0,0 +1,16 @@ +package com.abin.mallchat.common.user.mapper; + +import com.abin.mallchat.common.user.domain.entity.User; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * 用户表 Mapper 接口 + *

+ * + * @author abin + * @since 2023-03-19 + */ +public interface UserMapper extends BaseMapper { + +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/user/service/IItemConfigService.java b/mallchat-common/src/main/java/com/abin/mallchat/common/user/service/IItemConfigService.java new file mode 100644 index 0000000..57add27 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/user/service/IItemConfigService.java @@ -0,0 +1,16 @@ +package com.abin.mallchat.common.user.service; + +import com.abin.mallchat.common.user.domain.entity.ItemConfig; +import com.baomidou.mybatisplus.extension.service.IService; + +/** + *

+ * 功能物品配置表 服务类 + *

+ * + * @author abin + * @since 2023-03-19 + */ +public interface IItemConfigService extends IService { + +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/user/service/IUserBackpackService.java b/mallchat-common/src/main/java/com/abin/mallchat/common/user/service/IUserBackpackService.java new file mode 100644 index 0000000..4971b85 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/user/service/IUserBackpackService.java @@ -0,0 +1,27 @@ +package com.abin.mallchat.common.user.service; + +import com.abin.mallchat.common.common.domain.enums.IdempotentEnum; +import com.abin.mallchat.common.user.domain.entity.UserBackpack; +import com.abin.mallchat.common.user.domain.enums.ItemEnum; +import com.baomidou.mybatisplus.extension.service.IService; + +/** + *

+ * 用户背包表 服务类 + *

+ * + * @author abin + * @since 2023-03-19 + */ +public interface IUserBackpackService { + + + /** + * 用户获取一个物品 + * @param uid 用户id + * @param itemId 物品id + * @param idempotentEnum 幂等类型 + * @param businessId 上层业务发送的唯一标识 + */ + void acquireItem(Long uid, Long itemId, IdempotentEnum idempotentEnum, String businessId); +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/user/service/IpService.java b/mallchat-common/src/main/java/com/abin/mallchat/common/user/service/IpService.java new file mode 100644 index 0000000..c4b445e --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/user/service/IpService.java @@ -0,0 +1,10 @@ +package com.abin.mallchat.common.user.service; + +public interface IpService { + /** + * 异步更新用户ip详情 + * + * @param id + */ + void refreshIpDetailAsync(Long id); +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/user/service/cache/ItemCache.java b/mallchat-common/src/main/java/com/abin/mallchat/common/user/service/cache/ItemCache.java new file mode 100644 index 0000000..39ecf55 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/user/service/cache/ItemCache.java @@ -0,0 +1,42 @@ +package com.abin.mallchat.common.user.service.cache; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Pair; +import com.abin.mallchat.common.common.constant.RedisKey; +import com.abin.mallchat.common.common.domain.vo.request.CursorPageBaseReq; +import com.abin.mallchat.common.common.domain.vo.response.CursorPageBaseResp; +import com.abin.mallchat.common.common.utils.CursorUtils; +import com.abin.mallchat.common.common.utils.RedisUtils; +import com.abin.mallchat.common.user.dao.ItemConfigDao; +import com.abin.mallchat.common.user.dao.UserDao; +import com.abin.mallchat.common.user.domain.entity.ItemConfig; +import com.abin.mallchat.common.user.domain.entity.User; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Component; + +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * Description: 用户相关缓存 + * Author: abin + * Date: 2023-03-27 + */ +@Component +public class ItemCache {//todo 多级缓存 + + @Autowired + private ItemConfigDao itemConfigDao; + + @Cacheable(cacheNames = "item", key = "'itemsByType:'+#type") + public List getByType(Integer type) { + return itemConfigDao.getByType(type); + } + + @Cacheable(cacheNames = "item", key = "'item:'+#itemId") + public ItemConfig getById(Long itemId) { + return itemConfigDao.getById(itemId); + } +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/user/service/cache/UserCache.java b/mallchat-common/src/main/java/com/abin/mallchat/common/user/service/cache/UserCache.java new file mode 100644 index 0000000..dddb74d --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/user/service/cache/UserCache.java @@ -0,0 +1,112 @@ +package com.abin.mallchat.common.user.service.cache; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.lang.Pair; +import com.abin.mallchat.common.common.constant.RedisKey; +import com.abin.mallchat.common.common.domain.vo.request.CursorPageBaseReq; +import com.abin.mallchat.common.common.domain.vo.response.CursorPageBaseResp; +import com.abin.mallchat.common.common.utils.CursorUtils; +import com.abin.mallchat.common.common.utils.RedisUtils; +import com.abin.mallchat.common.user.dao.UserDao; +import com.abin.mallchat.common.user.domain.entity.User; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; + +import java.security.PublicKey; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Description: 用户相关缓存 + * Author: abin + * Date: 2023-03-27 + */ +@Component +public class UserCache { + + @Autowired + private RedisUtils redisUtils; + @Autowired + private CursorUtils cursorUtils; + @Autowired + private UserDao userDao; + + public Long getOnlineNum() { + String onlineKey = RedisKey.getKey(RedisKey.ONLINE_UID_ZET); + return redisUtils.zCard(onlineKey); + } + + public Long getOfflineNum() { + String offlineKey = RedisKey.getKey(RedisKey.OFFLINE_UID_ZET); + return redisUtils.zCard(offlineKey); + } + + //用户上线 + public void online(Long uid, Date optTime) { + String onlineKey = RedisKey.getKey(RedisKey.ONLINE_UID_ZET); + String offlineKey = RedisKey.getKey(RedisKey.OFFLINE_UID_ZET); + //移除离线表 + redisUtils.zRemove(offlineKey, uid); + //更新上线表 + redisUtils.zAdd(onlineKey, uid, optTime.getTime()); + } + + //用户下线 + public void offline(Long uid, Date optTime) { + String onlineKey = RedisKey.getKey(RedisKey.ONLINE_UID_ZET); + String offlineKey = RedisKey.getKey(RedisKey.OFFLINE_UID_ZET); + //移除上线线表 + redisUtils.zRemove(onlineKey, uid); + //更新上线表 + redisUtils.zAdd(offlineKey, uid, optTime.getTime()); + } + + public CursorPageBaseResp> getOnlineCursorPage(CursorPageBaseReq pageBaseReq) { + return cursorUtils.getCursorPageByRedis(pageBaseReq, RedisKey.getKey(RedisKey.ONLINE_UID_ZET), Long::parseLong); + } + + public CursorPageBaseResp> getOfflineCursorPage(CursorPageBaseReq pageBaseReq) { + return cursorUtils.getCursorPageByRedis(pageBaseReq, RedisKey.getKey(RedisKey.OFFLINE_UID_ZET), Long::parseLong); + } + + /** + * 获取用户信息,盘路缓存模式 + * + * @param uid + * @return + */ + public User getUserInfo(Long uid) {//todo 后期做二级缓存 + return getUserInfoBatch(Collections.singleton(uid)).get(uid); + } + + /** + * 获取用户信息,盘路缓存模式 + * + * @param uids + * @return + */ + public Map getUserInfoBatch(Set uids) { + List keys = uids.stream().map(a -> RedisKey.getKey(RedisKey.USER_INFO_STRING, a)).collect(Collectors.toList()); + List mget = redisUtils.mget(keys, User.class); + Map map = mget.stream().collect(Collectors.toMap(User::getId, Function.identity())); + //还需要load更新的uid + List needLoadUidList = uids.stream().filter(a -> !map.containsKey(a)).collect(Collectors.toList()); + if (CollUtil.isNotEmpty(needLoadUidList)) { + List needLoadUserList = userDao.listByIds(needLoadUidList); + Map redisMap = needLoadUserList.stream().collect(Collectors.toMap(a -> RedisKey.getKey(RedisKey.USER_INFO_STRING, a.getId()), Function.identity())); + redisUtils.mset(redisMap, 5 * 60); + map.putAll(needLoadUserList.stream().collect(Collectors.toMap(User::getId, Function.identity()))); + } + return map; + } + + public void delUserInfo(Long uid) { + String key = RedisKey.getKey(RedisKey.USER_INFO_STRING, uid); + redisUtils.del(key); + } + +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/user/service/impl/IpServiceImpl.java b/mallchat-common/src/main/java/com/abin/mallchat/common/user/service/impl/IpServiceImpl.java new file mode 100644 index 0000000..c41fca5 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/user/service/impl/IpServiceImpl.java @@ -0,0 +1,119 @@ +package com.abin.mallchat.common.user.service.impl; + +import cn.hutool.core.lang.TypeReference; +import cn.hutool.core.thread.NamedThreadFactory; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpUtil; +import cn.hutool.json.JSONUtil; +import com.abin.mallchat.common.common.domain.vo.response.ApiResult; +import com.abin.mallchat.common.user.dao.UserDao; +import com.abin.mallchat.common.user.domain.dto.IpResult; +import com.abin.mallchat.common.user.domain.entity.IpDetail; +import com.abin.mallchat.common.user.domain.entity.IpInfo; +import com.abin.mallchat.common.user.domain.entity.User; +import com.abin.mallchat.common.user.service.IpService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.stereotype.Service; + +import java.util.Date; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.*; +import java.util.jar.Manifest; + +/** + * Description: ip + * Author: abin + * Date: 2023-04-18 + */ +@Service +@Slf4j +public class IpServiceImpl implements IpService, DisposableBean { + private static ExecutorService executor = new ThreadPoolExecutor(1, 1, + 0L, TimeUnit.MILLISECONDS, + new LinkedBlockingQueue(500), new NamedThreadFactory("refresh-ipDetail", false)); + + @Autowired + private UserDao userDao; + + @Override + public void refreshIpDetailAsync(Long uid) { + executor.execute(() -> { + User user = userDao.getById(uid); + IpInfo ipInfo = user.getIpInfo(); + String ip = ipInfo.needRefreshIp(); + if (StrUtil.isBlank(ip)) { + return; + } + IpDetail ipDetail = TryGetIpDetailOrNullTreeTimes(ip); + if (Objects.nonNull(ipDetail)) { + ipInfo.refreshIpDetail(ipDetail); + User update = new User(); + update.setId(uid); + update.setIpInfo(ipInfo); + userDao.updateById(update); + } else { + log.error("get ip detail fail ip:{},uid:{}", ip, uid); + } + + }); + } + + private static IpDetail TryGetIpDetailOrNullTreeTimes(String ip) { + for (int i = 0; i < 3; i++) { + IpDetail ipDetail = getIpDetailOrNull(ip); + if (Objects.nonNull(ipDetail)) { + return ipDetail; + } + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + return null; + } + + public static IpDetail getIpDetailOrNull(String ip) { + String body = HttpUtil.get("https://ip.taobao.com/outGetIpInfo?ip=" + ip + "&accessKey=alibaba-inc"); + try { + IpResult result = JSONUtil.toBean(body, new TypeReference>() { + }, false); + if (result.isSuccess()) { + return result.getData(); + } + } catch (Exception ignored) { + } + return null; + } + + //测试耗时结果 100次查询总耗时约100s,平均一次成功查询需要1s,可以接受 + //第99次成功,目前耗时:99545ms + public static void main(String[] args) { + Date begin = new Date(); + for (int i = 0; i < 100; i++) { + int finalI = i; + executor.execute(() -> { + IpDetail ipDetail = TryGetIpDetailOrNullTreeTimes("113.90.36.126"); + if (Objects.nonNull(ipDetail)) { + Date date = new Date(); + System.out.println(String.format("第%d次成功,目前耗时:%dms", finalI, (date.getTime() - begin.getTime()))); + } + }); + } + } + + @Override + public void destroy() throws InterruptedException { + executor.shutdown(); + if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {//最多等30秒,处理不完就拉倒 + if (log.isErrorEnabled()) { + log.error("Timed out while waiting for executor [{}] to terminate", executor); + } + } + } +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/user/service/impl/UserBackpackServiceImpl.java b/mallchat-common/src/main/java/com/abin/mallchat/common/user/service/impl/UserBackpackServiceImpl.java new file mode 100644 index 0000000..01daeca --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/user/service/impl/UserBackpackServiceImpl.java @@ -0,0 +1,62 @@ +package com.abin.mallchat.common.user.service.impl; + +import com.abin.mallchat.common.common.domain.enums.IdempotentEnum; +import com.abin.mallchat.common.common.domain.enums.YesOrNoEnum; +import com.abin.mallchat.common.user.dao.ItemConfigDao; +import com.abin.mallchat.common.user.dao.UserBackpackDao; +import com.abin.mallchat.common.user.domain.entity.ItemConfig; +import com.abin.mallchat.common.user.domain.entity.UserBackpack; +import com.abin.mallchat.common.user.domain.enums.ItemTypeEnum; +import com.abin.mallchat.common.user.service.IUserBackpackService; +import com.abin.mallchat.common.user.service.cache.ItemCache; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Objects; + +/** + *

+ * 用户背包表 服务类 + *

+ * + * @author abin + * @since 2023-03-19 + */ +@Service +public class UserBackpackServiceImpl implements IUserBackpackService { + @Autowired + private UserBackpackDao userBackpackDao; + @Autowired + private ItemConfigDao itemConfigDao; + @Autowired + private ItemCache itemCache; + + @Override + public void acquireItem(Long uid, Long itemId, IdempotentEnum idempotentEnum, String businessId) {//todo 分布式锁 + String idempotent = getIdempotent(itemId, idempotentEnum, businessId); + UserBackpack userBackpack = userBackpackDao.getByIdp(idempotent); + //幂等检查 + if (Objects.nonNull(userBackpack)) { + return; + } + //业务检查 + ItemConfig itemConfig = itemCache.getById(itemId); + if (ItemTypeEnum.BADGE.getType().equals(itemConfig.getType())) {//徽章类型做唯一性检查 + Integer countByValidItemId = userBackpackDao.getCountByValidItemId(uid, itemId); + if (countByValidItemId > 0) {//已经有徽章了不发 + return; + } + } + //发物品 + UserBackpack insert = UserBackpack.builder() + .itemId(itemId) + .status(YesOrNoEnum.NO.getStatus()) + .idempotent(idempotent) + .build(); + userBackpackDao.save(insert); + } + + private String getIdempotent(Long itemId, IdempotentEnum idempotentEnum, String businessId) { + return String.format("%d_%d_%s", itemId, idempotentEnum.getType(), businessId); + } +} diff --git a/mallchat-common/src/main/resources/application-test.properties b/mallchat-common/src/main/resources/application-test.properties new file mode 100644 index 0000000..0943abe --- /dev/null +++ b/mallchat-common/src/main/resources/application-test.properties @@ -0,0 +1,21 @@ +#todo 记得把这些配置信息补充了 +##################mysql配置################## +mallchat.mysql.ip=127.0.0.1 +mallchat.mysql.port=3306 +mallchat.mysql.db=mallchat +mallchat.mysql.username=root +mallchat.mysql.password=123456 +##################redis配置################## +mallchat.redis.host=127.0.0.1 +mallchat.redis.port=6379 +mallchat.redis.password=123456 +##################jwt################## +mallchat.jwt.secret=dsfsdfsdfsdfsd +##################微信公众号信息################## +mallchat.wx.callback=http://127.0.0.1:8080 +mallchat.wx.appId=appid +mallchat.wx.secret=380bfc1c9147fdsf4sf07 +# 接口配置里的Token值 +mallchat.wx.token=sdfsf +# 接口配置里的EncodingAESKey值 +mallchat.wx.aesKey=sha1 \ No newline at end of file diff --git a/mallchat-common/src/main/resources/application.yml b/mallchat-common/src/main/resources/application.yml new file mode 100644 index 0000000..8b61c74 --- /dev/null +++ b/mallchat-common/src/main/resources/application.yml @@ -0,0 +1,60 @@ +logging: + level: + org.springframework.web: INFO + com.github.binarywang.demo.wx.mp: DEBUG + me.chanjar.weixin: DEBUG + +mybatis-plus: + mapper-locations: classpath:mapper/**/*.xml + configuration: + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + map-underscore-to-camel-case: true +spring: + profiles: + #运行的环境 + active: test + application: + name: mallchat + datasource: + url: jdbc:mysql://${mallchat.mysql.ip}:${mallchat.mysql.port}/${mallchat.mysql.db}?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai + username: ${mallchat.mysql.username} + password: ${mallchat.mysql.password} + driver-class-name: com.mysql.cj.jdbc.Driver + mvc: + pathmatch: + matching-strategy: ANT_PATH_MATCHER + redis: + # Redis服务器地址 + host: ${mallchat.redis.host} + # Redis服务器端口号 + port: ${mallchat.redis.port} + # 使用的数据库索引,默认是0 + database: 0 + # 连接超时时间 + timeout: 1800000 + # 设置密码 + password: ${mallchat.redis.password} + lettuce: + pool: + # 最大阻塞等待时间,负数表示没有限制 + max-wait: -1 + # 连接池中的最大空闲连接 + max-idle: 5 + # 连接池中的最小空闲连接 + min-idle: 0 + # 连接池中最大连接数,负数表示没有限制 + max-active: 20 + jackson: + serialization: + write-dates-as-timestamps: true +jwt: + secret: ${mallchat.jwt.secret} +wx: + mp: +# callback: http://f4cd-113-92-129-127.ngrok.io + callback: ${mallchat.wx.callback} + configs: + - appId: ${mallchat.wx.appId} # 第一个公众号的appid + secret: ${mallchat.wx.secret} # 公众号的appsecret + token: ${mallchat.wx.token} # 接口配置里的Token值 + aesKey: ${mallchat.wx.aesKey} # 接口配置里的EncodingAESKey值 \ No newline at end of file diff --git a/mallchat-common/src/main/resources/mapper/chat/MessageMapper.xml b/mallchat-common/src/main/resources/mapper/chat/MessageMapper.xml new file mode 100644 index 0000000..859974b --- /dev/null +++ b/mallchat-common/src/main/resources/mapper/chat/MessageMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/mallchat-common/src/main/resources/mapper/chat/MessageMarkMapper.xml b/mallchat-common/src/main/resources/mapper/chat/MessageMarkMapper.xml new file mode 100644 index 0000000..448c983 --- /dev/null +++ b/mallchat-common/src/main/resources/mapper/chat/MessageMarkMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/mallchat-common/src/main/resources/mapper/chat/RoomMapper.xml b/mallchat-common/src/main/resources/mapper/chat/RoomMapper.xml new file mode 100644 index 0000000..463b963 --- /dev/null +++ b/mallchat-common/src/main/resources/mapper/chat/RoomMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/mallchat-common/src/main/resources/mapper/user/ItemConfigMapper.xml b/mallchat-common/src/main/resources/mapper/user/ItemConfigMapper.xml new file mode 100644 index 0000000..32cbd60 --- /dev/null +++ b/mallchat-common/src/main/resources/mapper/user/ItemConfigMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/mallchat-common/src/main/resources/mapper/user/UserBackpackMapper.xml b/mallchat-common/src/main/resources/mapper/user/UserBackpackMapper.xml new file mode 100644 index 0000000..b9ac72d --- /dev/null +++ b/mallchat-common/src/main/resources/mapper/user/UserBackpackMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/mallchat-common/src/main/resources/mapper/user/UserMapper.xml b/mallchat-common/src/main/resources/mapper/user/UserMapper.xml new file mode 100644 index 0000000..ef1a567 --- /dev/null +++ b/mallchat-common/src/main/resources/mapper/user/UserMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/mallchat-custom-server/pom.xml b/mallchat-custom-server/pom.xml new file mode 100644 index 0000000..e2fd0c7 --- /dev/null +++ b/mallchat-custom-server/pom.xml @@ -0,0 +1,29 @@ + + + + mallchat + com.abin.mallchat + 1.0-SNAPSHOT + + 4.0.0 + + mallchat-custom-server + + + + com.abin.mallchat + mallchat-common + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + \ No newline at end of file diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/MallchatCustomApplication.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/MallchatCustomApplication.java new file mode 100644 index 0000000..4ca05aa --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/MallchatCustomApplication.java @@ -0,0 +1,22 @@ +package com.abin.mallchat.custom; + +import com.abin.mallchat.custom.user.controller.WxPortalController; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.web.servlet.ServletComponentScan; + +/** + * @author zhongzb + * @date 2021/05/27 + */ +@SpringBootApplication(scanBasePackages = {"com.abin.mallchat"}) +@MapperScan({"com.abin.mallchat.common.**.mapper" }) +@ServletComponentScan +public class MallchatCustomApplication { + + public static void main(String[] args) { + SpringApplication.run(MallchatCustomApplication.class); + } + +} \ No newline at end of file diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/controller/ChatController.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/controller/ChatController.java new file mode 100644 index 0000000..68b8f60 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/controller/ChatController.java @@ -0,0 +1,78 @@ +package com.abin.mallchat.custom.chat.controller; + + +import com.abin.mallchat.common.common.annotation.FrequencyControl; +import com.abin.mallchat.common.common.domain.vo.request.CursorPageBaseReq; +import com.abin.mallchat.common.common.domain.vo.response.ApiResult; +import com.abin.mallchat.common.common.domain.vo.response.CursorPageBaseResp; +import com.abin.mallchat.common.common.domain.vo.response.IdRespVO; +import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessageMarkReq; +import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessagePageReq; +import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessageReq; +import com.abin.mallchat.custom.chat.domain.vo.response.ChatMemberResp; +import com.abin.mallchat.custom.chat.domain.vo.response.ChatMemberStatisticResp; +import com.abin.mallchat.custom.chat.domain.vo.response.ChatMessageResp; +import com.abin.mallchat.custom.chat.domain.vo.response.ChatRoomResp; +import com.abin.mallchat.custom.chat.service.ChatService; +import com.abin.mallchat.common.common.utils.RequestHolder; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; + +/** + *

+ * 群聊相关接口 + *

+ * + * @author abin + * @since 2023-03-19 + */ +@RestController +@RequestMapping("/capi/chat") +@Api(tags = "聊天室相关接口") +public class ChatController { + @Autowired + private ChatService chatService; + @GetMapping("/public/room/page") + @ApiOperation("会话列表") + public ApiResult> getRoomPage(CursorPageBaseReq request) { + return ApiResult.success(chatService.getRoomPage(request, RequestHolder.get().getUid())); + } + @GetMapping("/public/member/page") + @ApiOperation("群成员列表") + public ApiResult> getMemberPage(CursorPageBaseReq request) { + return ApiResult.success(chatService.getMemberPage(request)); + } + + @GetMapping("/public/member/statistic") + @ApiOperation("群成员人数统计") + public ApiResult getMemberStatistic() { + return ApiResult.success(chatService.getMemberStatistic()); + } + + @GetMapping("/public/msg/page") + @ApiOperation("消息列表") + public ApiResult> getMsgPage(ChatMessagePageReq request) { + return ApiResult.success(chatService.getMsgPage(request, RequestHolder.get().getUid())); + } + + @PostMapping("/msg") + @ApiOperation("发送消息") + @FrequencyControl(time = 5,count = 2) + @FrequencyControl(time = 30,count = 5) + @FrequencyControl(time = 60,count = 10) + public ApiResult sendMsg(@Valid @RequestBody ChatMessageReq request) { + return ApiResult.success(IdRespVO.id(chatService.sendMsg(request, RequestHolder.get().getUid()))); + } + + @PutMapping("/msg/mark") + @ApiOperation("消息标记") + public ApiResult setMsgMark(@Valid @RequestBody ChatMessageMarkReq request) {//分布式锁 + chatService.setMsgMark(RequestHolder.get().getUid(),request); + return ApiResult.success(); + } +} + diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/domain/vo/request/ChatMessageMarkReq.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/domain/vo/request/ChatMessageMarkReq.java new file mode 100644 index 0000000..70e4b0a --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/domain/vo/request/ChatMessageMarkReq.java @@ -0,0 +1,34 @@ +package com.abin.mallchat.custom.chat.domain.vo.request; + +import com.abin.mallchat.common.common.domain.vo.request.CursorPageBaseReq; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import javax.validation.constraints.NotNull; + +/** + * Description: 消息标记请求 + * Author: abin + * Date: 2023-03-29 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class ChatMessageMarkReq{ + @NotNull + @ApiModelProperty("消息id") + private Long msgId; + + @NotNull + @ApiModelProperty("标记类型 1点赞 2举报") + private Integer markType; + + @NotNull + @ApiModelProperty("动作类型 1确认 2取消") + private Integer actType; +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/domain/vo/request/ChatMessagePageReq.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/domain/vo/request/ChatMessagePageReq.java new file mode 100644 index 0000000..331102d --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/domain/vo/request/ChatMessagePageReq.java @@ -0,0 +1,26 @@ +package com.abin.mallchat.custom.chat.domain.vo.request; + +import com.abin.mallchat.common.common.domain.vo.request.CursorPageBaseReq; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import javax.validation.constraints.NotNull; + +/** + * Description: 消息列表请求 + * Author: abin + * Date: 2023-03-29 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class ChatMessagePageReq extends CursorPageBaseReq { + @NotNull + @ApiModelProperty("会话id") + private Long roomId; +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/domain/vo/request/ChatMessageReq.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/domain/vo/request/ChatMessageReq.java new file mode 100644 index 0000000..295f9b0 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/domain/vo/request/ChatMessageReq.java @@ -0,0 +1,36 @@ +package com.abin.mallchat.custom.chat.domain.vo.request; + +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; +import org.hibernate.validator.constraints.Length; + +import javax.validation.constraints.NotNull; + + +/** + * Description: 消息发送请求体 + * Author: abin + * Date: 2023-03-23 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class ChatMessageReq { + + @NotNull + @Length( max = 10000,message = "消息内容过长,服务器扛不住啊,兄dei") + @ApiModelProperty("消息内容") + private String content; + + @NotNull + @ApiModelProperty("会话id") + private Long roomId; + + @ApiModelProperty("回复的消息id,如果没有别传就好") + private Long replyMsgId; +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/domain/vo/response/ChatMemberResp.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/domain/vo/response/ChatMemberResp.java new file mode 100644 index 0000000..7178f07 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/domain/vo/response/ChatMemberResp.java @@ -0,0 +1,36 @@ +package com.abin.mallchat.custom.chat.domain.vo.response; + +import io.swagger.annotations.ApiModelProperty; +import io.swagger.annotations.ApiOperation; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import java.util.Date; + +/** + * Description: 群成员列表的成员信息 + * Author: abin + * Date: 2023-03-23 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class ChatMemberResp { + @ApiModelProperty("uid") + private Long uid; + @ApiModelProperty("用户名称") + private String name; + @ApiModelProperty("头像") + private String avatar; + /** + * @see com.abin.mallchat.common.user.domain.enums.ChatActiveStatusEnum + */ + @ApiModelProperty("在线状态 1在线 2离线") + private Integer activeStatus; + @ApiModelProperty("最后一次上下线时间") + private Date lastOptTime; +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/domain/vo/response/ChatMemberStatisticResp.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/domain/vo/response/ChatMemberStatisticResp.java new file mode 100644 index 0000000..cd9ea08 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/domain/vo/response/ChatMemberStatisticResp.java @@ -0,0 +1,27 @@ +package com.abin.mallchat.custom.chat.domain.vo.response; + +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import java.util.Date; + +/** + * Description: 群成员统计信息 + * Author: abin + * Date: 2023-03-23 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class ChatMemberStatisticResp { + + @ApiModelProperty("在线人数") + private Long onlineNum;//在线人数 + @ApiModelProperty("总人数") + private Long totalNum;//总人数 +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/domain/vo/response/ChatMessageResp.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/domain/vo/response/ChatMessageResp.java new file mode 100644 index 0000000..498dfa2 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/domain/vo/response/ChatMessageResp.java @@ -0,0 +1,94 @@ +package com.abin.mallchat.custom.chat.domain.vo.response; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import java.time.LocalDateTime; +import java.util.Date; +import java.util.List; + +/** + * Description: 消息 + * Author: abin + * Date: 2023-03-23 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class ChatMessageResp { + + @ApiModelProperty("发送者信息") + private UserInfo fromUser; + @ApiModelProperty("消息详情") + private Message message; + + @Data + public static class UserInfo { + @ApiModelProperty("用户名称") + private String username; + @ApiModelProperty("用户id") + private Long uid; + @ApiModelProperty("头像") + private String avatar; + @ApiModelProperty("徽章标识,如果没有展示null") + private Badge badge; + } + + @Data + public static class Message { + @ApiModelProperty("消息id") + private Long id; + @ApiModelProperty("消息发送时间") + private Date sendTime; + @ApiModelProperty("消息内容") + private String content; + @ApiModelProperty("消息类型 1正常文本 2.爆赞 (点赞超过10)3.危险发言(举报超5)") + private Integer type; + @ApiModelProperty("消息标记") + private MessageMark messageMark; + @ApiModelProperty("父消息,如果没有父消息,返回的是null") + private ReplyMsg reply; + + } + + @Data + public static class ReplyMsg { + @ApiModelProperty("消息id") + private Long id; + @ApiModelProperty("用户名称") + private String username; + @ApiModelProperty("消息内容") + private String content; + @ApiModelProperty("是否可消息跳转 0否 1是") + private Integer canCallback; + @ApiModelProperty("跳转间隔的消息条数") + private Integer gapCount; + } + + @Data + public static class MessageMark { + @ApiModelProperty("点赞数") + private Integer likeCount; + @ApiModelProperty("该用户是否已经点赞 0否 1是") + private Integer userLike; + @ApiModelProperty("举报数") + private Integer dislikeCount; + @ApiModelProperty("该用户是否已经举报 0否 1是") + private Integer userDislike; + } + @Data + private static class Badge { + @ApiModelProperty("徽章图像") + private String img; + @ApiModelProperty("徽章说明") + private String describe; + } +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/domain/vo/response/ChatRoomResp.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/domain/vo/response/ChatRoomResp.java new file mode 100644 index 0000000..6467685 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/domain/vo/response/ChatRoomResp.java @@ -0,0 +1,30 @@ +package com.abin.mallchat.custom.chat.domain.vo.response; + +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import java.util.Date; + +/** + * Description: 群成员列表的成员信息 + * Author: abin + * Date: 2023-03-23 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class ChatRoomResp { + @ApiModelProperty("会话id") + private Long id; + @ApiModelProperty("会话名称") + private String name; + @ApiModelProperty("会话类型 1大群聊 2沸点") + private Integer type; + @ApiModelProperty("房间最后活跃时间") + private Date lastActiveTime; +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/ChatService.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/ChatService.java new file mode 100644 index 0000000..ad67b41 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/ChatService.java @@ -0,0 +1,68 @@ +package com.abin.mallchat.custom.chat.service; + +import com.abin.mallchat.common.chat.dao.MessageDao; +import com.abin.mallchat.common.chat.domain.entity.Message; +import com.abin.mallchat.common.common.domain.vo.request.CursorPageBaseReq; +import com.abin.mallchat.common.common.domain.vo.response.CursorPageBaseResp; +import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessageMarkReq; +import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessagePageReq; +import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessageReq; +import com.abin.mallchat.custom.chat.domain.vo.response.ChatMemberResp; +import com.abin.mallchat.custom.chat.domain.vo.response.ChatMemberStatisticResp; +import com.abin.mallchat.custom.chat.domain.vo.response.ChatMessageResp; +import com.abin.mallchat.custom.chat.domain.vo.response.ChatRoomResp; +import com.abin.mallchat.custom.chat.service.adapter.MessageAdapter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.annotation.Nullable; + +/** + * Description: 消息处理类 + * Author: abin + * Date: 2023-03-26 + */ +public interface ChatService { + + /** + * 发送消息 + * + * @param request + */ + Long sendMsg(ChatMessageReq request, Long uid); + + /** + * 根据消息获取消息前端展示的物料 + * @param message + * @param receiveUid 接受消息的uid,可null + * @return + */ + ChatMessageResp getMsgResp(Message message,Long receiveUid); + + /** + * 获取群成员列表 + * @param request + * @return + */ + CursorPageBaseResp getMemberPage(CursorPageBaseReq request); + + /** + * 获取消息列表 + * @param request + * @return + */ + CursorPageBaseResp getMsgPage(ChatMessagePageReq request,@Nullable Long receiveUid); + + /** + * 获取会话列表 + * @param request + * @param uid + * @return + */ + CursorPageBaseResp getRoomPage(CursorPageBaseReq request, Long uid); + + ChatMemberStatisticResp getMemberStatistic(); + + void setMsgMark(Long uid, ChatMessageMarkReq request); +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/adapter/MemberAdapter.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/adapter/MemberAdapter.java new file mode 100644 index 0000000..93dd8c6 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/adapter/MemberAdapter.java @@ -0,0 +1,46 @@ +package com.abin.mallchat.custom.chat.service.adapter; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.lang.Pair; +import com.abin.mallchat.common.chat.domain.entity.Message; +import com.abin.mallchat.common.chat.domain.enums.MessageStatusEnum; +import com.abin.mallchat.common.user.domain.entity.User; +import com.abin.mallchat.common.user.domain.enums.ChatActiveStatusEnum; +import com.abin.mallchat.common.user.service.cache.UserCache; +import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessageReq; +import com.abin.mallchat.custom.chat.domain.vo.response.ChatMemberResp; +import com.abin.mallchat.custom.chat.domain.vo.response.ChatMessageResp; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.Collection; +import java.util.Date; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * Description: 成员适配器 + * Author: abin + * Date: 2023-03-26 + */ +@Component +@Slf4j +public class MemberAdapter { + @Autowired + private UserCache userCache; + + public List buildMember(List> list, ChatActiveStatusEnum statusEnum) { + return list.stream().map(a -> { + ChatMemberResp resp = new ChatMemberResp(); + resp.setActiveStatus(statusEnum.getStatus()); + resp.setLastOptTime(new Date(a.getValue().longValue())); + resp.setUid(a.getKey()); + User userInfo = userCache.getUserInfo(a.getKey()); + resp.setName(userInfo.getName()); + resp.setAvatar(userInfo.getAvatar()); + return resp; + }).collect(Collectors.toList()); + } +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/adapter/MessageAdapter.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/adapter/MessageAdapter.java new file mode 100644 index 0000000..8b59806 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/adapter/MessageAdapter.java @@ -0,0 +1,92 @@ +package com.abin.mallchat.custom.chat.service.adapter; + +import cn.hutool.core.bean.BeanUtil; +import com.abin.mallchat.common.chat.domain.entity.Message; +import com.abin.mallchat.common.chat.domain.entity.MessageMark; +import com.abin.mallchat.common.chat.domain.enums.MessageMarkTypeEnum; +import com.abin.mallchat.common.chat.domain.enums.MessageStatusEnum; +import com.abin.mallchat.common.chat.domain.enums.MessageTypeEnum; +import com.abin.mallchat.common.common.domain.enums.YesOrNoEnum; +import com.abin.mallchat.common.user.domain.entity.User; +import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessageReq; +import com.abin.mallchat.custom.chat.domain.vo.response.ChatMessageResp; +import com.sun.org.apache.regexp.internal.RE; +import org.yaml.snakeyaml.error.Mark; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * Description: 消息适配器 + * Author: abin + * Date: 2023-03-26 + */ +public class MessageAdapter { + public static final int CAN_CALLBACK_GAP_COUNT = 100; + + public static Message buildMsgSave(ChatMessageReq request, Long uid) { + return Message.builder() + .replyMsgId(request.getReplyMsgId()) + .content(request.getContent()) + .fromUid(uid) + .roomId(request.getRoomId()) + .status(MessageStatusEnum.NORMAL.getStatus()) + .build(); + + } + + public static List buildMsgResp(List messages, Map replyMap, Map userMap, List msgMark, Long receiveUid) { + Map> markMap = msgMark.stream().collect(Collectors.groupingBy(MessageMark::getMsgId)); + return messages.stream().map(a -> { + ChatMessageResp resp = new ChatMessageResp(); + resp.setFromUser(buildFromUser(userMap.get(a.getFromUid()))); + resp.setMessage(buildMessage(a, replyMap, userMap, markMap.getOrDefault(a.getId(), new ArrayList<>()), receiveUid)); + return resp; + }) + .sorted(Comparator.comparing(a -> a.getMessage().getSendTime()))//帮前端排好序,更方便它展示 + .collect(Collectors.toList()); + } + + private static ChatMessageResp.Message buildMessage(Message message, Map replyMap, Map userMap, List marks, Long receiveUid) { + ChatMessageResp.Message messageVO = new ChatMessageResp.Message(); + BeanUtil.copyProperties(message, messageVO); + messageVO.setSendTime(message.getCreateTime()); + Message replyMessage = replyMap.get(message.getReplyMsgId()); + //回复消息 + if (Objects.nonNull(replyMessage)) { + ChatMessageResp.ReplyMsg replyMsgVO = new ChatMessageResp.ReplyMsg(); + replyMsgVO.setId(replyMessage.getId()); + replyMsgVO.setContent(replyMessage.getContent()); + User replyUser = userMap.get(replyMessage.getFromUid()); + replyMsgVO.setUsername(replyUser.getName()); + replyMsgVO.setCanCallback(YesOrNoEnum.toStatus(Objects.nonNull(message.getGapCount()) && message.getGapCount() <= CAN_CALLBACK_GAP_COUNT)); + replyMsgVO.setGapCount(message.getGapCount()); + messageVO.setReply(replyMsgVO); + } + //消息标记 + messageVO.setMessageMark(buildMsgMark(marks, receiveUid)); + return messageVO; + } + + private static ChatMessageResp.MessageMark buildMsgMark(List marks, Long receiveUid) { + Map> typeMap = marks.stream().collect(Collectors.groupingBy(MessageMark::getType)); + List likeMarks = typeMap.getOrDefault(MessageMarkTypeEnum.LIKE.getType(), new ArrayList<>()); + List dislikeMarks = typeMap.getOrDefault(MessageMarkTypeEnum.DISLIKE.getType(), new ArrayList<>()); + ChatMessageResp.MessageMark mark = new ChatMessageResp.MessageMark(); + mark.setLikeCount(likeMarks.size()); + mark.setUserLike(Optional.ofNullable(receiveUid).filter(uid -> likeMarks.stream().anyMatch(a -> a.getUid().equals(uid))).map(a -> YesOrNoEnum.YES.getStatus()).orElse(YesOrNoEnum.NO.getStatus())); + mark.setDislikeCount(dislikeMarks.size()); + mark.setUserDislike(Optional.ofNullable(receiveUid).filter(uid -> dislikeMarks.stream().anyMatch(a -> a.getUid().equals(uid))).map(a -> YesOrNoEnum.YES.getStatus()).orElse(YesOrNoEnum.NO.getStatus())); + return mark; + } + + private static ChatMessageResp.UserInfo buildFromUser(User fromUser) { + ChatMessageResp.UserInfo userInfo = new ChatMessageResp.UserInfo(); + userInfo.setUsername(fromUser.getName()); + userInfo.setAvatar(fromUser.getAvatar()); + userInfo.setUid(fromUser.getId()); + return userInfo; + } + + +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/adapter/RoomAdapter.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/adapter/RoomAdapter.java new file mode 100644 index 0000000..372cae9 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/adapter/RoomAdapter.java @@ -0,0 +1,34 @@ +package com.abin.mallchat.custom.chat.service.adapter; + +import cn.hutool.core.bean.BeanUtil; +import com.abin.mallchat.common.chat.domain.entity.Message; +import com.abin.mallchat.common.chat.domain.entity.Room; +import com.abin.mallchat.common.chat.domain.enums.MessageStatusEnum; +import com.abin.mallchat.common.user.domain.entity.User; +import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessageReq; +import com.abin.mallchat.custom.chat.domain.vo.response.ChatMessageResp; +import com.abin.mallchat.custom.chat.domain.vo.response.ChatRoomResp; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * Description: 消息适配器 + * Author: abin + * Date: 2023-03-26 + */ +public class RoomAdapter { + + + public static List buildResp(List list) { + return list.stream() + .map(a -> { + ChatRoomResp resp = new ChatRoomResp(); + BeanUtil.copyProperties(a, resp); + resp.setLastActiveTime(a.getActiveTime()); + return resp; + }).collect(Collectors.toList()); + } +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/helper/ChatMemberHelper.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/helper/ChatMemberHelper.java new file mode 100644 index 0000000..b809282 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/helper/ChatMemberHelper.java @@ -0,0 +1,30 @@ +package com.abin.mallchat.custom.chat.service.helper; + +import cn.hutool.core.lang.Pair; +import cn.hutool.core.util.StrUtil; +import com.abin.mallchat.common.user.domain.enums.ChatActiveStatusEnum; + +/** + * Description: 成员列表工具类 + * Author: abin + * Date: 2023-03-28 + */ +public class ChatMemberHelper { + private static final String SEPARATOR = "_"; + + public static Pair getCursorPair(String cursor) { + ChatActiveStatusEnum activeStatusEnum = ChatActiveStatusEnum.ONLINE; + String timeCursor = null; + if (StrUtil.isNotBlank(cursor)) { + String activeStr = cursor.split(SEPARATOR)[0]; + String timeStr = cursor.split(SEPARATOR)[1]; + activeStatusEnum = ChatActiveStatusEnum.of(Integer.parseInt(activeStr)); + timeCursor = timeStr; + } + return Pair.of(activeStatusEnum, timeCursor); + } + + public static String generateCursor(ChatActiveStatusEnum activeStatusEnum, String timeCursor) { + return activeStatusEnum.getStatus() + SEPARATOR + timeCursor; + } +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/impl/ChatServiceImpl.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/impl/ChatServiceImpl.java new file mode 100644 index 0000000..1838c79 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/impl/ChatServiceImpl.java @@ -0,0 +1,213 @@ +package com.abin.mallchat.custom.chat.service.impl; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.lang.Pair; +import com.abin.mallchat.common.chat.dao.MessageDao; +import com.abin.mallchat.common.chat.dao.MessageMarkDao; +import com.abin.mallchat.common.chat.dao.RoomDao; +import com.abin.mallchat.common.chat.domain.entity.Message; +import com.abin.mallchat.common.chat.domain.entity.MessageMark; +import com.abin.mallchat.common.chat.domain.entity.Room; +import com.abin.mallchat.common.common.domain.enums.YesOrNoEnum; +import com.abin.mallchat.common.common.domain.vo.request.CursorPageBaseReq; +import com.abin.mallchat.common.common.domain.vo.response.CursorPageBaseResp; +import com.abin.mallchat.common.common.exception.BusinessException; +import com.abin.mallchat.common.common.utils.AssertUtil; +import com.abin.mallchat.common.user.dao.UserDao; +import com.abin.mallchat.common.user.domain.entity.User; +import com.abin.mallchat.common.user.domain.enums.ChatActiveStatusEnum; +import com.abin.mallchat.common.user.service.cache.UserCache; +import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessageMarkReq; +import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessagePageReq; +import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessageReq; +import com.abin.mallchat.custom.chat.domain.vo.response.ChatMemberResp; +import com.abin.mallchat.custom.chat.domain.vo.response.ChatMemberStatisticResp; +import com.abin.mallchat.custom.chat.domain.vo.response.ChatMessageResp; +import com.abin.mallchat.custom.chat.domain.vo.response.ChatRoomResp; +import com.abin.mallchat.custom.chat.service.adapter.MemberAdapter; +import com.abin.mallchat.custom.chat.service.adapter.RoomAdapter; +import com.abin.mallchat.custom.chat.service.helper.ChatMemberHelper; +import com.abin.mallchat.custom.common.event.MessageMarkEvent; +import com.abin.mallchat.custom.common.event.MessageSendEvent; +import com.abin.mallchat.custom.chat.service.ChatService; +import com.abin.mallchat.custom.chat.service.adapter.MessageAdapter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Description: 消息处理类 + * Author: abin + * Date: 2023-03-26 + */ +@Service +@Slf4j +public class ChatServiceImpl implements ChatService { + public static final long ROOM_GROUP_ID = 1L; + @Autowired + private MessageDao messageDao; + @Autowired + private UserDao userDao; + @Autowired + private ApplicationEventPublisher applicationEventPublisher; + @Autowired + private UserCache userCache; + @Autowired + private MemberAdapter memberAdapter; + @Autowired + private RoomDao roomDao; + @Autowired + private MessageMarkDao messageMarkDao; + + /** + * 发送消息 + * + * @param request + */ + @Override + @Transactional + public Long sendMsg(ChatMessageReq request, Long uid) { + //校验下回复消息 + Message replyMsg =null; + if(Objects.nonNull(request.getReplyMsgId())){ + replyMsg = messageDao.getById(request.getReplyMsgId()); + AssertUtil.isNotEmpty(replyMsg,"回复消息不存在"); + AssertUtil.equal(replyMsg.getRoomId(),request.getRoomId(),"只能回复相同会话内的消息"); + + } + Message insert = MessageAdapter.buildMsgSave(request, uid); + messageDao.save(insert); + //如果有回复消息 + if(Objects.nonNull(replyMsg)){ + Integer gapCount = messageDao.getGapCount(request.getRoomId(), replyMsg.getId(), insert.getId()); + messageDao.updateGapCount(insert.getId(),gapCount); + } + //发布消息发送事件 + applicationEventPublisher.publishEvent(new MessageSendEvent(this, insert.getId())); + return insert.getId(); + } + + @Override + public ChatMessageResp getMsgResp(Message message, Long receiveUid) { + return CollUtil.getFirst(getMsgRespBatch(Collections.singletonList(message), receiveUid)); + } + + @Override + public CursorPageBaseResp getMemberPage(CursorPageBaseReq request) { + Pair pair = ChatMemberHelper.getCursorPair(request.getCursor()); + ChatActiveStatusEnum activeStatusEnum = pair.getKey(); + String timeCursor = pair.getValue(); + List resultList = new ArrayList<>();//最终列表 + Boolean isLast = Boolean.FALSE; + if (activeStatusEnum == ChatActiveStatusEnum.ONLINE) {//在线列表 + CursorPageBaseResp> cursorPage = userCache.getOnlineCursorPage(new CursorPageBaseReq(request.getPageSize(), timeCursor)); + resultList.addAll(memberAdapter.buildMember(cursorPage.getList(), ChatActiveStatusEnum.ONLINE));//添加在线列表 + if (cursorPage.getIsLast()) {//如果是最后一页,从离线列表再补点数据 + Integer leftSize = request.getPageSize() - cursorPage.getList().size(); + cursorPage = userCache.getOfflineCursorPage(new CursorPageBaseReq(leftSize, null)); + resultList.addAll(memberAdapter.buildMember(cursorPage.getList(), ChatActiveStatusEnum.OFFLINE));//添加离线线列表 + activeStatusEnum = ChatActiveStatusEnum.OFFLINE; + } + timeCursor = cursorPage.getCursor(); + isLast = cursorPage.getIsLast(); + } else if (activeStatusEnum == ChatActiveStatusEnum.OFFLINE) {//离线列表 + CursorPageBaseResp> cursorPage = userCache.getOfflineCursorPage(new CursorPageBaseReq(request.getPageSize(), timeCursor)); + resultList.addAll(memberAdapter.buildMember(cursorPage.getList(), ChatActiveStatusEnum.OFFLINE));//添加离线线列表 + timeCursor = cursorPage.getCursor(); + isLast = cursorPage.getIsLast(); + } + //组装结果 + return new CursorPageBaseResp<>(ChatMemberHelper.generateCursor(activeStatusEnum, timeCursor), isLast, resultList); + } + + @Override + public CursorPageBaseResp getMsgPage(ChatMessagePageReq request, Long receiveUid) { + CursorPageBaseResp cursorPage = messageDao.getCursorPage(request.getRoomId(), request); + if (cursorPage.isEmpty()) { + return CursorPageBaseResp.empty(); + } + return CursorPageBaseResp.init(cursorPage, getMsgRespBatch(cursorPage.getList(), receiveUid)); + } + + @Override + public CursorPageBaseResp getRoomPage(CursorPageBaseReq request, Long uid) { + CursorPageBaseResp cursorPage = roomDao.getCursorPage(request); + if (request.isFirstPage()) { + //第一页插入置顶的大群聊 + Room group = roomDao.getById(ROOM_GROUP_ID); + cursorPage.getList().add(0, group); + } + return CursorPageBaseResp.init(cursorPage, RoomAdapter.buildResp(cursorPage.getList())); + } + + @Override + public ChatMemberStatisticResp getMemberStatistic() { + System.out.println(Thread.currentThread().getName()); + Long onlineNum = userCache.getOnlineNum(); + Long offlineNum = userCache.getOfflineNum(); + ChatMemberStatisticResp resp = new ChatMemberStatisticResp(); + resp.setOnlineNum(onlineNum); + resp.setTotalNum(onlineNum + offlineNum); + return resp; + } + + @Override + public void setMsgMark(Long uid, ChatMessageMarkReq request) { + //用户对该消息的标记 + MessageMark messageMark = messageMarkDao.get(uid, request.getMsgId(), request.getMarkType()); + if (Objects.nonNull(messageMark)) {//有标记过消息修改一下就好 + MessageMark update = MessageMark.builder() + .id(messageMark.getId()) + .status(transformAct(request.getActType())) + .build(); + messageMarkDao.updateById(update); + } + //没标记过消息,插入一条新消息 + MessageMark insert = MessageMark.builder() + .uid(uid) + .msgId(request.getMsgId()) + .type(request.getMarkType()) + .status(transformAct(request.getActType())) + .build(); + messageMarkDao.save(insert); + //发布消息标记事件 + applicationEventPublisher.publishEvent(new MessageMarkEvent(this,request)); + } + + private Integer transformAct(Integer actType) { + if (actType == 1) { + return YesOrNoEnum.NO.getStatus(); + } else if (actType == 2) { + return YesOrNoEnum.YES.getStatus(); + } + throw new BusinessException("动作类型 1确认 2取消"); + } + + public List getMsgRespBatch(List messages, Long receiveUid) { + if (CollectionUtil.isEmpty(messages)) { + return new ArrayList<>(); + } + Map replyMap = new HashMap<>(); + Map userMap = new HashMap<>(); + //批量查出回复的消息 + List replyIds = messages.stream().map(Message::getReplyMsgId).filter(Objects::nonNull).distinct().collect(Collectors.toList()); + if (CollectionUtil.isNotEmpty(replyIds)) { + replyMap = messageDao.listByIds(replyIds).stream().collect(Collectors.toMap(Message::getId, Function.identity())); + } + //批量查询消息关联用户 + Set uidSet = Stream.concat(replyMap.values().stream().map(Message::getFromUid), messages.stream().map(Message::getFromUid)).collect(Collectors.toSet()); + userMap = userCache.getUserInfoBatch(uidSet); + //查询消息标志 + List msgMark = messageMarkDao.getValidMarkByMsgIdBatch(messages.stream().map(Message::getId).collect(Collectors.toList())); + return MessageAdapter.buildMsgResp(messages, replyMap, userMap,msgMark,receiveUid); + } + +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/config/InterceptorConfig.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/config/InterceptorConfig.java new file mode 100644 index 0000000..09bf88e --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/config/InterceptorConfig.java @@ -0,0 +1,29 @@ +package com.abin.mallchat.custom.common.config; + +import com.abin.mallchat.custom.common.intecepter.CollectorInterceptor; +import com.abin.mallchat.custom.common.intecepter.TokenInterceptor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * Description: 配置所有拦截器 + * Author: abin + * Date: 2023-04-05 + */ +@Configuration +public class InterceptorConfig implements WebMvcConfigurer { + + @Autowired + private TokenInterceptor tokenInterceptor; + @Autowired + private CollectorInterceptor collectorInterceptor; + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(tokenInterceptor) + .addPathPatterns("/capi/**"); + registry.addInterceptor(collectorInterceptor) + .addPathPatterns("/capi/**"); + } +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/config/SwaggerConfig.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/config/SwaggerConfig.java new file mode 100644 index 0000000..796657e --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/config/SwaggerConfig.java @@ -0,0 +1,67 @@ +package com.abin.mallchat.custom.common.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.core.env.Environment; +import org.springframework.core.env.Profiles; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import springfox.documentation.builders.ApiInfoBuilder; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.service.Contact; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spring.web.plugins.Docket; +import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc; + +/** + * Description: + * Author: abin + * Date: 2023-03-23 + */ +@Configuration +@EnableSwagger2WebMvc +public class SwaggerConfig { + @Bean(value = "defaultApi2") + Docket docket() { + return new Docket(DocumentationType.SWAGGER_2) + //配置网站的基本信息 + .apiInfo(new ApiInfoBuilder() + //网站标题 + .title("mallchat接口文档") + //标题后面的版本号 + .version("v1.0") + .description("mallchat接口文档") + //联系人信息 + .contact(new Contact("阿斌", "http://www.mallchat.cn", "972627721@qq.com")) + .build()) + .select() + //指定接口的位置 + .apis(RequestHandlerSelectors + .withClassAnnotation(RestController.class) + ) + .paths(PathSelectors.any()) + .build(); + } + /** + * swagger 配置 + * @param environment 环境 + */ +// @Bean +// public Docket docket(Environment environment) { +// +// // 设置环境范围 +// Profiles profiles = Profiles.of("dev","test"); +// // 如果在该环境返回内则返回:true,反之返回 false +// boolean flag = environment.acceptsProfiles(profiles); +// +// // 创建一个 swagger 的 bean 实例 +// return new Docket(DocumentationType.SWAGGER_2) +// .enable(flag) // 是否开启 swagger:true -> 开启,false -> 关闭 +// ; +// } + +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/config/WxMpConfiguration.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/config/WxMpConfiguration.java new file mode 100644 index 0000000..6196427 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/config/WxMpConfiguration.java @@ -0,0 +1,84 @@ +package com.abin.mallchat.custom.common.config; + +import com.abin.mallchat.custom.user.service.handler.*; +import lombok.AllArgsConstructor; +import me.chanjar.weixin.common.redis.JedisWxRedisOps; +import me.chanjar.weixin.mp.api.WxMpMessageRouter; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl; +import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl; +import me.chanjar.weixin.mp.config.impl.WxMpRedisConfigImpl; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; + +import java.util.List; +import java.util.stream.Collectors; + +import static me.chanjar.weixin.common.api.WxConsts.EventType; +import static me.chanjar.weixin.common.api.WxConsts.EventType.SUBSCRIBE; +import static me.chanjar.weixin.common.api.WxConsts.EventType.UNSUBSCRIBE; +import static me.chanjar.weixin.common.api.WxConsts.XmlMsgType; +import static me.chanjar.weixin.common.api.WxConsts.XmlMsgType.EVENT; +import static me.chanjar.weixin.mp.constant.WxMpEventConstants.CustomerService.*; +import static me.chanjar.weixin.mp.constant.WxMpEventConstants.POI_CHECK_NOTIFY; + +/** + * wechat mp configuration + * + * @author Binary Wang + */ +@AllArgsConstructor +@Configuration +@EnableConfigurationProperties(WxMpProperties.class) +public class WxMpConfiguration { + private final LogHandler logHandler; + private final MsgHandler msgHandler; + private final SubscribeHandler subscribeHandler; + private final ScanHandler scanHandler; + private final WxMpProperties properties; + + @Bean + public WxMpService wxMpService() { + // 代码里 getConfigs()处报错的同学,请注意仔细阅读项目说明,你的IDE需要引入lombok插件!!!! + final List configs = this.properties.getConfigs(); + if (configs == null) { + throw new RuntimeException("大哥,拜托先看下项目首页的说明(readme文件),添加下相关配置,注意别配错了!"); + } + + WxMpService service = new WxMpServiceImpl(); + service.setMultiConfigStorages(configs + .stream().map(a -> { + WxMpDefaultConfigImpl configStorage; + configStorage = new WxMpDefaultConfigImpl(); + + configStorage.setAppId(a.getAppId()); + configStorage.setSecret(a.getSecret()); + configStorage.setToken(a.getToken()); + configStorage.setAesKey(a.getAesKey()); + return configStorage; + }).collect(Collectors.toMap(WxMpDefaultConfigImpl::getAppId, a -> a, (o, n) -> o))); + return service; + } + + @Bean + public WxMpMessageRouter messageRouter(WxMpService wxMpService) { + final WxMpMessageRouter newRouter = new WxMpMessageRouter(wxMpService); + + // 记录所有事件的日志 (异步执行) + newRouter.rule().handler(this.logHandler).next(); + + // 关注事件 + newRouter.rule().async(false).msgType(EVENT).event(SUBSCRIBE).handler(this.subscribeHandler).end(); + + // 扫码事件 + newRouter.rule().async(false).msgType(EVENT).event(EventType.SCAN).handler(this.scanHandler).end(); + + // 默认 + newRouter.rule().async(false).handler(this.msgHandler).end(); + + return newRouter; + } + +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/config/WxMpProperties.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/config/WxMpProperties.java new file mode 100644 index 0000000..a7a53e9 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/config/WxMpProperties.java @@ -0,0 +1,82 @@ +package com.abin.mallchat.custom.common.config; + +import cn.hutool.json.JSONUtil; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.util.List; + +/** + * wechat mp properties + * + * @author Binary Wang + */ +@Data +@ConfigurationProperties(prefix = "wx.mp") +public class WxMpProperties { + /** + * 是否使用redis存储access token + */ + private boolean useRedis; + + /** + * redis 配置 + */ + private RedisConfig redisConfig; + + @Data + public static class RedisConfig { + /** + * redis服务器 主机地址 + */ + private String host; + + /** + * redis服务器 端口号 + */ + private Integer port; + + /** + * redis服务器 密码 + */ + private String password; + + /** + * redis 服务连接超时时间 + */ + private Integer timeout; + } + + /** + * 多个公众号配置信息 + */ + private List configs; + + @Data + public static class MpConfig { + /** + * 设置微信公众号的appid + */ + private String appId; + + /** + * 设置微信公众号的app secret + */ + private String secret; + + /** + * 设置微信公众号的token + */ + private String token; + + /** + * 设置微信公众号的EncodingAESKey + */ + private String aesKey; + } + + @Override + public String toString() { + return JSONUtil.toJsonStr(this); + } +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/event/MessageMarkEvent.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/event/MessageMarkEvent.java new file mode 100644 index 0000000..6bd4f8b --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/event/MessageMarkEvent.java @@ -0,0 +1,15 @@ +package com.abin.mallchat.custom.common.event; + +import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessageMarkReq; +import lombok.Getter; +import org.springframework.context.ApplicationEvent; + +@Getter +public class MessageMarkEvent extends ApplicationEvent { + private ChatMessageMarkReq req; + + public MessageMarkEvent(Object source, ChatMessageMarkReq req) { + super(source); + this.req = req; + } +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/event/MessageSendEvent.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/event/MessageSendEvent.java new file mode 100644 index 0000000..ace9788 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/event/MessageSendEvent.java @@ -0,0 +1,15 @@ +package com.abin.mallchat.custom.common.event; + +import com.abin.mallchat.common.chat.domain.entity.Message; +import lombok.Getter; +import org.springframework.context.ApplicationEvent; + +@Getter +public class MessageSendEvent extends ApplicationEvent { + private Long msgId; + + public MessageSendEvent(Object source, Long msgId) { + super(source); + this.msgId = msgId; + } +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/event/UserOfflineEvent.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/event/UserOfflineEvent.java new file mode 100644 index 0000000..8a295af --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/event/UserOfflineEvent.java @@ -0,0 +1,15 @@ +package com.abin.mallchat.custom.common.event; + +import com.abin.mallchat.common.user.domain.entity.User; +import lombok.Getter; +import org.springframework.context.ApplicationEvent; + +@Getter +public class UserOfflineEvent extends ApplicationEvent { + private User user; + + public UserOfflineEvent(Object source, User user) { + super(source); + this.user = user; + } +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/event/UserOnlineEvent.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/event/UserOnlineEvent.java new file mode 100644 index 0000000..9ac1bb5 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/event/UserOnlineEvent.java @@ -0,0 +1,17 @@ +package com.abin.mallchat.custom.common.event; + +import com.abin.mallchat.common.user.domain.entity.User; +import lombok.Getter; +import org.springframework.context.ApplicationEvent; + +import java.util.Date; + +@Getter +public class UserOnlineEvent extends ApplicationEvent { + private User user; + + public UserOnlineEvent(Object source, User user) { + super(source); + this.user = user; + } +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/event/UserRegisterEvent.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/event/UserRegisterEvent.java new file mode 100644 index 0000000..f3242f0 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/event/UserRegisterEvent.java @@ -0,0 +1,15 @@ +package com.abin.mallchat.custom.common.event; + +import com.abin.mallchat.common.user.domain.entity.User; +import lombok.Getter; +import org.springframework.context.ApplicationEvent; + +@Getter +public class UserRegisterEvent extends ApplicationEvent { + private User user; + + public UserRegisterEvent(Object source, User user) { + super(source); + this.user = user; + } +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/event/listener/MessageMarkListener.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/event/listener/MessageMarkListener.java new file mode 100644 index 0000000..96710d3 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/event/listener/MessageMarkListener.java @@ -0,0 +1,56 @@ +package com.abin.mallchat.custom.common.event.listener; + +import com.abin.mallchat.common.chat.dao.MessageDao; +import com.abin.mallchat.common.chat.dao.MessageMarkDao; +import com.abin.mallchat.common.chat.domain.entity.Message; +import com.abin.mallchat.common.chat.domain.enums.MessageMarkTypeEnum; +import com.abin.mallchat.common.chat.domain.enums.MessageTypeEnum; +import com.abin.mallchat.common.common.domain.enums.IdempotentEnum; +import com.abin.mallchat.common.user.domain.enums.ItemEnum; +import com.abin.mallchat.common.user.service.IUserBackpackService; +import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessageMarkReq; +import com.abin.mallchat.custom.common.event.MessageMarkEvent; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +import java.util.Objects; + +/** + * 消息标记监听器 + * + * @author zhongzb create on 2022/08/26 + */ +@Slf4j +@Component +public class MessageMarkListener { + @Autowired + private MessageMarkDao messageMarkDao; + @Autowired + private MessageDao messageDao; + @Autowired + private IUserBackpackService iUserBackpackService; + + @Async + @EventListener(classes = MessageMarkEvent.class) + public void changeMsgType(MessageMarkEvent event) { + ChatMessageMarkReq req = event.getReq(); + Message msg = messageDao.getById(req.getMsgId()); + if (!Objects.equals(msg, MessageTypeEnum.NORMAL.getType())) {//普通消息才需要升级 + return; + } + //消息被标记次数 + Integer markCount = messageMarkDao.getMarkCount(req.getMsgId(), req.getMarkType()); + MessageMarkTypeEnum markTypeEnum = MessageMarkTypeEnum.of(req.getMarkType()); + if (markCount < markTypeEnum.getRiseNum()) { + return; + } + boolean updateSuccess = messageDao.riseOptimistic(msg.getId(), msg.getType(), markTypeEnum.getRiseEnum().getType()); + if (MessageMarkTypeEnum.LIKE.getType().equals(req.getMarkType()) && updateSuccess) {//尝试给用户发送一张徽章 + iUserBackpackService.acquireItem(msg.getFromUid(), ItemEnum.LIKE_BADGE.getId(), IdempotentEnum.MSG_ID, msg.getId().toString()); + } + } + +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/event/listener/MessageSendListener.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/event/listener/MessageSendListener.java new file mode 100644 index 0000000..45fc314 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/event/listener/MessageSendListener.java @@ -0,0 +1,39 @@ +package com.abin.mallchat.custom.common.event.listener; + +import com.abin.mallchat.common.chat.dao.MessageDao; +import com.abin.mallchat.common.chat.domain.entity.Message; +import com.abin.mallchat.custom.chat.domain.vo.response.ChatMessageResp; +import com.abin.mallchat.custom.common.event.MessageSendEvent; +import com.abin.mallchat.custom.chat.service.ChatService; +import com.abin.mallchat.custom.user.service.WebSocketService; +import com.abin.mallchat.custom.user.service.adapter.WSAdapter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +/** + * 消息发送监听器 + * + * @author zhongzb create on 2022/08/26 + */ +@Slf4j +@Component +public class MessageSendListener { + @Autowired + private WebSocketService webSocketService; + @Autowired + private ChatService chatService; + @Autowired + private MessageDao messageDao; + + @Async + @EventListener(classes = MessageSendEvent.class) + public void notifyAllOnline(MessageSendEvent event) { + Message message = messageDao.getById(event.getMsgId()); + ChatMessageResp msgResp = chatService.getMsgResp(message, null); + webSocketService.sendToAllOnline(WSAdapter.buildMsgSend(msgResp), message.getFromUid()); + } + +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/event/listener/UserOfflineListener.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/event/listener/UserOfflineListener.java new file mode 100644 index 0000000..077c5c7 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/event/listener/UserOfflineListener.java @@ -0,0 +1,52 @@ +package com.abin.mallchat.custom.common.event.listener; + +import com.abin.mallchat.common.user.dao.UserDao; +import com.abin.mallchat.common.user.domain.entity.User; +import com.abin.mallchat.common.user.service.cache.UserCache; +import com.abin.mallchat.custom.common.event.UserOfflineEvent; +import com.abin.mallchat.custom.common.event.UserOnlineEvent; +import com.abin.mallchat.custom.user.service.WebSocketService; +import com.abin.mallchat.custom.user.service.adapter.WSAdapter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +/** + * 用户下线监听器 + * + * @author zhongzb create on 2022/08/26 + */ +@Slf4j +@Component +public class UserOfflineListener { + @Autowired + private WebSocketService webSocketService; + @Autowired + private UserDao userDao; + @Autowired + private UserCache userCache; + @Autowired + private WSAdapter wsAdapter; + + @Async + @EventListener(classes = UserOfflineEvent.class) + public void saveRedisAndPush(UserOfflineEvent event) { + User user = event.getUser(); + userCache.offline(user.getId(), user.getLastOptTime()); + //推送给所有在线用户,该用户下线 + webSocketService.sendToAllOnline(wsAdapter.buildOfflineNotifyResp(event.getUser()), event.getUser().getId()); + } + + @Async + @EventListener(classes = UserOfflineEvent.class) + public void saveDB(UserOfflineEvent event) { + User user = event.getUser(); + User update = new User(); + update.setId(user.getId()); + update.setLastOptTime(user.getLastOptTime()); + userDao.updateById(update); + } + +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/event/listener/UserOnlineListener.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/event/listener/UserOnlineListener.java new file mode 100644 index 0000000..aa4469b --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/event/listener/UserOnlineListener.java @@ -0,0 +1,62 @@ +package com.abin.mallchat.custom.common.event.listener; + +import com.abin.mallchat.common.chat.dao.MessageDao; +import com.abin.mallchat.common.chat.domain.entity.Message; +import com.abin.mallchat.common.user.dao.UserDao; +import com.abin.mallchat.common.user.domain.entity.User; +import com.abin.mallchat.common.user.service.IpService; +import com.abin.mallchat.common.user.service.cache.UserCache; +import com.abin.mallchat.custom.chat.domain.vo.response.ChatMessageResp; +import com.abin.mallchat.custom.chat.service.ChatService; +import com.abin.mallchat.custom.common.event.MessageSendEvent; +import com.abin.mallchat.custom.common.event.UserOnlineEvent; +import com.abin.mallchat.custom.user.service.WebSocketService; +import com.abin.mallchat.custom.user.service.adapter.WSAdapter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +/** + * 用户上线监听器 + * + * @author zhongzb create on 2022/08/26 + */ +@Slf4j +@Component +public class UserOnlineListener { + @Autowired + private WebSocketService webSocketService; + @Autowired + private UserDao userDao; + @Autowired + private UserCache userCache; + @Autowired + private WSAdapter wsAdapter; + @Autowired + private IpService ipService; + + @Async + @EventListener(classes = UserOnlineEvent.class) + public void saveRedisAndPush(UserOnlineEvent event) { + User user = event.getUser(); + userCache.online(user.getId(), user.getLastOptTime()); + //推送给所有在线用户,该用户登录成功 + webSocketService.sendToAllOnline(wsAdapter.buildOnlineNotifyResp(event.getUser()), event.getUser().getId()); + } + + @Async + @EventListener(classes = UserOnlineEvent.class) + public void saveDB(UserOnlineEvent event) { + User user = event.getUser(); + User update = new User(); + update.setId(user.getId()); + update.setLastOptTime(user.getLastOptTime()); + update.setIpInfo(user.getIpInfo()); + userDao.updateById(update); + //更新用户ip详情 + ipService.refreshIpDetailAsync(user.getId()); + } + +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/event/listener/UserRegisterListener.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/event/listener/UserRegisterListener.java new file mode 100644 index 0000000..34217ac --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/event/listener/UserRegisterListener.java @@ -0,0 +1,53 @@ +package com.abin.mallchat.custom.common.event.listener; + +import com.abin.mallchat.common.common.domain.enums.IdempotentEnum; +import com.abin.mallchat.common.user.dao.UserDao; +import com.abin.mallchat.common.user.domain.entity.User; +import com.abin.mallchat.common.user.domain.enums.ItemEnum; +import com.abin.mallchat.common.user.service.IUserBackpackService; +import com.abin.mallchat.common.user.service.cache.UserCache; +import com.abin.mallchat.custom.common.event.UserOnlineEvent; +import com.abin.mallchat.custom.common.event.UserRegisterEvent; +import com.abin.mallchat.custom.user.service.WebSocketService; +import com.abin.mallchat.custom.user.service.adapter.WSAdapter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +/** + * 用户上线监听器 + * + * @author zhongzb create on 2022/08/26 + */ +@Slf4j +@Component +public class UserRegisterListener { + @Autowired + private WebSocketService webSocketService; + @Autowired + private UserDao userDao; + @Autowired + private UserCache userCache; + @Autowired + private WSAdapter wsAdapter; + @Autowired + private IUserBackpackService iUserBackpackService; + + @Async + @EventListener(classes = UserRegisterEvent.class) + public void sendCard(UserRegisterEvent event) { + User user = event.getUser(); + //送一张改名卡 + iUserBackpackService.acquireItem(user.getId(), ItemEnum.MODIFY_NAME_CARD.getId(), IdempotentEnum.UID, user.getId().toString()); + } + + @Async + @EventListener(classes = UserRegisterEvent.class) + public void sendBadge(UserOnlineEvent event) { + User user = event.getUser(); + int count = userDao.count(); + } + +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/intecepter/CollectorInterceptor.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/intecepter/CollectorInterceptor.java new file mode 100644 index 0000000..c81294a --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/intecepter/CollectorInterceptor.java @@ -0,0 +1,45 @@ +package com.abin.mallchat.custom.common.intecepter; + +import cn.hutool.extra.servlet.ServletUtil; +import com.abin.mallchat.common.common.utils.RequestHolder; +import com.abin.mallchat.common.common.domain.dto.RequestInfo; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Optional; + +/** + * 信息收集的拦截器 + */ +@Order +@Slf4j +@Component +public class CollectorInterceptor implements HandlerInterceptor, WebMvcConfigurer { + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(this) + .addPathPatterns("/**"); + } + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + RequestInfo info = new RequestInfo(); + info.setUid(Optional.ofNullable(request.getAttribute(TokenInterceptor.ATTRIBUTE_UID)).map(Object::toString).map(Long::parseLong).orElse(null)); + info.setIp(ServletUtil.getClientIP(request)); + RequestHolder.set(info); + return true; + } + + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { + RequestHolder.remove(); + } + +} \ No newline at end of file diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/intecepter/HttpTraceIdFilter.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/intecepter/HttpTraceIdFilter.java new file mode 100644 index 0000000..3659965 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/intecepter/HttpTraceIdFilter.java @@ -0,0 +1,33 @@ +package com.abin.mallchat.custom.common.intecepter; + +import com.abin.mallchat.common.common.constant.MDCKey; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.MDC; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.WebApplicationContextUtils; + +import javax.servlet.*; +import javax.servlet.FilterConfig; +import javax.servlet.annotation.WebFilter; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.util.UUID; + +/** + * Description: 设置链路追踪的值,初期单体项目先简单用 + * Author: abin + * Date: 2023-04-05 + */ +@Slf4j +@WebFilter(urlPatterns = "/*") +public class HttpTraceIdFilter implements Filter { + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + String tid = UUID.randomUUID().toString(); + MDC.put(MDCKey.TID, tid); + chain.doFilter(request, response); + } + +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/intecepter/TokenInterceptor.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/intecepter/TokenInterceptor.java new file mode 100644 index 0000000..fba7381 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/intecepter/TokenInterceptor.java @@ -0,0 +1,77 @@ +package com.abin.mallchat.custom.common.intecepter; + +import cn.hutool.http.HttpUtil; +import com.abin.mallchat.common.common.constant.MDCKey; +import com.abin.mallchat.common.common.exception.HttpErrorEnum; +import com.abin.mallchat.custom.user.service.LoginService; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.MDC; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.annotation.Order; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@Order(-2) +@Slf4j +@Component +public class TokenInterceptor implements HandlerInterceptor { + + public static final String AUTHORIZATION_HEADER = "Authorization"; + public static final String AUTHORIZATION_SCHEMA = "Bearer "; + public static final String ATTRIBUTE_UID = "uid"; + + @Autowired + private LoginService loginService; + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + //获取用户登录token + String token = getToken(request); + Long validUid = loginService.getValidUid(token); + if (Objects.nonNull(validUid)) {//有登录态 + request.setAttribute(ATTRIBUTE_UID, validUid); + } else { + boolean isPublicURI = isPublicURI(request.getRequestURI()); + if (!isPublicURI) {//又没有登录态,又不是公开路径,直接401 + HttpErrorEnum.ACCESS_DENIED.sendHttpError(response); + return false; + } + } + MDC.put(MDCKey.UID, String.valueOf(validUid)); + return true; + } + + /** + * 判断是不是公共方法,可以未登录访问的 + * + * @param requestURI + */ + private boolean isPublicURI(String requestURI) { + String[] split = requestURI.split("/"); + return split.length > 2 && "public".equals(split[3]); + } + + private String getToken(HttpServletRequest request) { + String header = request.getHeader(AUTHORIZATION_HEADER); + return Optional.ofNullable(header) + .filter(h -> h.startsWith(AUTHORIZATION_SCHEMA)) + .map(h -> h.substring(AUTHORIZATION_SCHEMA.length())) + .orElse(null); + } +} \ No newline at end of file diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/intecepter/WebLogAspect.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/intecepter/WebLogAspect.java new file mode 100644 index 0000000..c3b686a --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/intecepter/WebLogAspect.java @@ -0,0 +1,74 @@ +package com.abin.mallchat.custom.common.intecepter; + +import cn.hutool.core.date.StopWatch; +import cn.hutool.json.JSONUtil; +import com.abin.mallchat.common.common.utils.RequestHolder; +import com.abin.mallchat.common.common.domain.dto.RequestInfo; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + + +/** + * 日志切面 + * + * @author wayne + */ +@Aspect +@Slf4j +@Component +public class WebLogAspect { + + + /** + * 接收到请求,记录请求内容 + * 所有controller包下所有的类的方法,都是切点 + *

+ * 如果ApiResult返回success=false,则打印warn日志; + * warn日志只能打印在同一行,因为只有等到ApiResult结果才知道是success=false。 + *

+ * 如果ApiResult返回success=true,则打印info日志; + * 特别注意:由于info级别日志已经包含了warn级别日志。如果开了info级别日志,warn就不会打印了。 + */ + @Around("execution(* com..controller..*.*(..))") + public Object around(ProceedingJoinPoint joinPoint) throws Throwable { + HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest(); + String method = request.getMethod(); + String uri = request.getRequestURI(); + //如果参数有HttpRequest,ServletResponse,直接移除,不打印这些 + List paramList = Stream.of(joinPoint.getArgs()) + .filter(args -> !(args instanceof ServletRequest)) + .filter(args -> !(args instanceof ServletResponse)) + .collect(Collectors.toList()); + String printParamStr = paramList.size() == 1 ? JSONUtil.toJsonStr(paramList.get(0)) : JSONUtil.toJsonStr(paramList); + RequestInfo requestInfo = RequestHolder.get(); + String userHeaderStr = JSONUtil.toJsonStr(requestInfo); + if (log.isInfoEnabled()) { + log.info("[{}][{}]【base:{}】【request:{}】", method, uri, userHeaderStr, printParamStr); + } + StopWatch stopWatch = new StopWatch(); + stopWatch.start(); + Object result = joinPoint.proceed(); + stopWatch.stop(); + long cost = stopWatch.getTotalTimeMillis(); + String printResultStr = JSONUtil.toJsonStr(result); + if (log.isInfoEnabled()) { + log.info("[{}]【response:{}】[cost:{}ms]", uri, printResultStr, cost); + } + return result; + } + + +} \ No newline at end of file diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/controller/UserController.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/controller/UserController.java new file mode 100644 index 0000000..8e957de --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/controller/UserController.java @@ -0,0 +1,60 @@ +package com.abin.mallchat.custom.user.controller; + + +import com.abin.mallchat.common.common.domain.vo.response.ApiResult; +import com.abin.mallchat.common.common.utils.RequestHolder; +import com.abin.mallchat.custom.user.domain.vo.request.user.ModifyNameReq; +import com.abin.mallchat.custom.user.domain.vo.request.user.WearingBadgeReq; +import com.abin.mallchat.custom.user.domain.vo.response.user.BadgeResp; +import com.abin.mallchat.custom.user.service.UserService; +import com.abin.mallchat.custom.user.domain.vo.response.user.UserInfoResp; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; +import java.util.List; + +/** + *

+ * 用户表 前端控制器 + *

+ * + * @author abin + * @since 2023-03-19 + */ +@RestController +@RequestMapping("/capi/user") +@Api(tags = "用户管理相关接口") +public class UserController { + @Autowired + private UserService userService; + + @GetMapping("/userInfo") + @ApiOperation("用户详情") + public ApiResult getUserInfo() { + return ApiResult.success(userService.getUserInfo(RequestHolder.get().getUid())); + } + + @PutMapping("/name") + @ApiOperation("修改用户名") + public ApiResult modifyName(@Valid @RequestBody ModifyNameReq req) { + userService.modifyName(RequestHolder.get().getUid(), req); + return ApiResult.success(); + } + + @GetMapping("/badges") + @ApiOperation("可选徽章预览") + public ApiResult> badges() { + return ApiResult.success(userService.badges(RequestHolder.get().getUid())); + } + + @PutMapping("/badge") + @ApiOperation("佩戴徽章") + public ApiResult wearingBadge(@Valid @RequestBody WearingBadgeReq req) { + userService.wearingBadge(RequestHolder.get().getUid(),req); + return ApiResult.success(); + } +} + diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/controller/WxPortalController.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/controller/WxPortalController.java new file mode 100644 index 0000000..47279b6 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/controller/WxPortalController.java @@ -0,0 +1,118 @@ +package com.abin.mallchat.custom.user.controller; + +import com.abin.mallchat.custom.user.service.WxMsgService; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.bean.WxOAuth2UserInfo; +import me.chanjar.weixin.common.bean.oauth2.WxOAuth2AccessToken; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.mp.api.WxMpMessageRouter; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; +import org.apache.commons.lang3.StringUtils; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.view.RedirectView; + +/** + * Description: 微信api交互接口 + * Author: abin + * Date: 2023-03-19 + */ +@Slf4j +@AllArgsConstructor +@RestController +@RequestMapping("wx/portal/public") +public class WxPortalController { + + private final WxMpService wxService; + private final WxMpMessageRouter messageRouter; + private final WxMsgService wxMsgService; + + @GetMapping(produces = "text/plain;charset=utf-8") + public String authGet(@RequestParam(name = "signature", required = false) String signature, + @RequestParam(name = "timestamp", required = false) String timestamp, + @RequestParam(name = "nonce", required = false) String nonce, + @RequestParam(name = "echostr", required = false) String echostr) { + + log.info("\n接收到来自微信服务器的认证消息:[{}, {}, {}, {}]", signature, + timestamp, nonce, echostr); + if (StringUtils.isAnyBlank(signature, timestamp, nonce, echostr)) { + throw new IllegalArgumentException("请求参数非法,请核实!"); + } + + + if (wxService.checkSignature(timestamp, nonce, signature)) { + return echostr; + } + + return "非法请求"; + } + + @GetMapping("/callBack") + public RedirectView callBack(@RequestParam String code) throws WxErrorException { + try { + WxOAuth2AccessToken accessToken = wxService.getOAuth2Service().getAccessToken(code); + WxOAuth2UserInfo userInfo = wxService.getOAuth2Service().getUserInfo(accessToken, "zh_CN"); + wxMsgService.authorize(userInfo); + }catch (Exception e){ + log.error("callBack error",e); + } + RedirectView redirectView = new RedirectView(); + redirectView.setUrl("https://mp.weixin.qq.com/s/MKCWzoCIzvh5G_1sK5sLoA"); + return redirectView; + } + + @PostMapping(produces = "application/xml; charset=UTF-8") + public String post(@RequestBody String requestBody, + @RequestParam("signature") String signature, + @RequestParam("timestamp") String timestamp, + @RequestParam("nonce") String nonce, + @RequestParam("openid") String openid, + @RequestParam(name = "encrypt_type", required = false) String encType, + @RequestParam(name = "msg_signature", required = false) String msgSignature) { + log.info("\n接收微信请求:[openid=[{}], [signature=[{}], encType=[{}], msgSignature=[{}]," + + " timestamp=[{}], nonce=[{}], requestBody=[\n{}\n] ", + openid, signature, encType, msgSignature, timestamp, nonce, requestBody); + + if (!wxService.checkSignature(timestamp, nonce, signature)) { + throw new IllegalArgumentException("非法请求,可能属于伪造的请求!"); + } + + String out = null; + if (encType == null) { + // 明文传输的消息 + WxMpXmlMessage inMessage = WxMpXmlMessage.fromXml(requestBody); + WxMpXmlOutMessage outMessage = this.route(inMessage); + if (outMessage == null) { + return ""; + } + + out = outMessage.toXml(); + } else if ("aes".equalsIgnoreCase(encType)) { + // aes加密的消息 + WxMpXmlMessage inMessage = WxMpXmlMessage.fromEncryptedXml(requestBody, wxService.getWxMpConfigStorage(), + timestamp, nonce, msgSignature); + log.debug("\n消息解密后内容为:\n{} ", inMessage.toString()); + WxMpXmlOutMessage outMessage = this.route(inMessage); + if (outMessage == null) { + return ""; + } + + out = outMessage.toEncryptedXml(wxService.getWxMpConfigStorage()); + } + + log.debug("\n组装回复信息:{}", out); + return out; + } + + private WxMpXmlOutMessage route(WxMpXmlMessage message) { + try { + return this.messageRouter.route(message); + } catch (Exception e) { + log.error("路由消息时出现异常!", e); + } + + return null; + } +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/dto/ws/WSChannelExtraDTO.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/dto/ws/WSChannelExtraDTO.java new file mode 100644 index 0000000..f3eae5c --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/dto/ws/WSChannelExtraDTO.java @@ -0,0 +1,25 @@ +package com.abin.mallchat.custom.user.domain.dto.ws; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import java.util.Date; + +/** + * Description: 记录和前端连接的一些映射信息 + * Author: abin + * Date: 2023-03-21 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class WSChannelExtraDTO { + /** + * 前端如果登录了,记录uid + */ + private Long uid; +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/enums/WSReqTypeEnum.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/enums/WSReqTypeEnum.java new file mode 100644 index 0000000..8e76df7 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/enums/WSReqTypeEnum.java @@ -0,0 +1,36 @@ +package com.abin.mallchat.custom.user.domain.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * Description: ws前端请求类型枚举 + * Author: abin + * Date: 2023-03-19 + */ +@AllArgsConstructor +@Getter +public enum WSReqTypeEnum { + LOGIN(1, "请求登录二维码"), + HEARTBEAT(2, "心跳包"), + AUTHORIZE(3, "登录认证"), + ; + + private final Integer type; + private final String desc; + + private static Map cache; + + static { + cache = Arrays.stream(WSReqTypeEnum.values()).collect(Collectors.toMap(WSReqTypeEnum::getType, Function.identity())); + } + + public static WSReqTypeEnum of(Integer type) { + return cache.get(type); + } +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/enums/WSRespTypeEnum.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/enums/WSRespTypeEnum.java new file mode 100644 index 0000000..6fa7453 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/enums/WSRespTypeEnum.java @@ -0,0 +1,44 @@ +package com.abin.mallchat.custom.user.domain.enums; + +import com.abin.mallchat.custom.user.domain.vo.response.ws.WSLoginSuccess; +import com.abin.mallchat.custom.user.domain.vo.response.ws.WSLoginUrl; +import com.abin.mallchat.custom.user.domain.vo.response.ws.WSMessage; +import com.abin.mallchat.custom.user.domain.vo.response.ws.WSOnlineOfflineNotify; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * Description: ws前端请求类型枚举 + * Author: abin + * Date: 2023-03-19 + */ +@AllArgsConstructor +@Getter +public enum WSRespTypeEnum { + LOGIN_URL(1, "登录二维码返回", WSLoginUrl.class), + LOGIN_SCAN_SUCCESS(2, "用户扫描成功等待授权", null), + LOGIN_SUCCESS(3, "用户登录成功返回用户信息", WSLoginSuccess.class), + MESSAGE(4, "新消息", WSMessage.class), + ONLINE_OFFLINE_NOTIFY(5, "上下线通知", WSOnlineOfflineNotify.class), + INVALIDATE_TOKEN(6, "使前端的token失效,意味着前端需要重新登录", null), + ; + + private final Integer type; + private final String desc; + private final Class dataClass; + + private static Map cache; + + static { + cache = Arrays.stream(WSRespTypeEnum.values()).collect(Collectors.toMap(WSRespTypeEnum::getType, Function.identity())); + } + + public static WSRespTypeEnum of(Integer type) { + return cache.get(type); + } +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/request/user/ModifyNameReq.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/request/user/ModifyNameReq.java new file mode 100644 index 0000000..bb3e5a3 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/request/user/ModifyNameReq.java @@ -0,0 +1,30 @@ +package com.abin.mallchat.custom.user.domain.vo.request.user; + +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; +import org.hibernate.validator.constraints.Length; + +import javax.validation.constraints.NotNull; + + +/** + * Description: 修改用户名 + * Author: abin + * Date: 2023-03-23 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class ModifyNameReq { + + @NotNull + @Length(max = 6,message = "用户名可别取太长,不然我记不住噢") + @ApiModelProperty("用户名") + private String name; + +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/request/user/WearingBadgeReq.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/request/user/WearingBadgeReq.java new file mode 100644 index 0000000..38251ec --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/request/user/WearingBadgeReq.java @@ -0,0 +1,29 @@ +package com.abin.mallchat.custom.user.domain.vo.request.user; + +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; +import org.hibernate.validator.constraints.Length; + +import javax.validation.constraints.NotNull; + + +/** + * Description: 佩戴徽章 + * Author: abin + * Date: 2023-03-23 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class WearingBadgeReq { + + @NotNull + @ApiModelProperty("徽章id") + private Long badgeId; + +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/request/ws/WSAuthorize.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/request/ws/WSAuthorize.java new file mode 100644 index 0000000..c62fd55 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/request/ws/WSAuthorize.java @@ -0,0 +1,20 @@ +package com.abin.mallchat.custom.user.domain.vo.request.ws; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +/** + * Description: + * Author: abin + * Date: 2023-03-19 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class WSAuthorize { + private String token; +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/request/ws/WSBaseReq.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/request/ws/WSBaseReq.java new file mode 100644 index 0000000..8f8f431 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/request/ws/WSBaseReq.java @@ -0,0 +1,22 @@ +package com.abin.mallchat.custom.user.domain.vo.request.ws; + +import lombok.Data; + +/** + * Description: websocket前端请求体 + * Author: abin + * Date: 2023-03-19 + */ +@Data +public class WSBaseReq { + /** + * 请求类型 1.请求登录二维码,2心跳检测 + * @see com.abin.mallchat.custom.user.domain.enums.WSReqTypeEnum + */ + private Integer type; + + /** + * 每个请求包具体的数据,类型不同结果不同 + */ + private String data; +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/response/user/BadgeResp.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/response/user/BadgeResp.java new file mode 100644 index 0000000..9a28c40 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/response/user/BadgeResp.java @@ -0,0 +1,30 @@ +package com.abin.mallchat.custom.user.domain.vo.response.user; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * Description: 徽章信息 + * Author: abin + * Date: 2023-03-22 + */ +@Data +@ApiModel("徽章信息") +public class BadgeResp { + + @ApiModelProperty(value = "徽章id") + private Long id; + + @ApiModelProperty(value = "徽章图标") + private String image; + + @ApiModelProperty(value = "徽章描述") + private String describe; + + @ApiModelProperty(value = "是否拥有 0否 1是") + private Integer obtain; + + @ApiModelProperty(value = "是否佩戴 0否 1是") + private Integer wearing; +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/response/user/UserInfoResp.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/response/user/UserInfoResp.java new file mode 100644 index 0000000..9dbc230 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/response/user/UserInfoResp.java @@ -0,0 +1,31 @@ +package com.abin.mallchat.custom.user.domain.vo.response.user; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * Description: 用户信息返回 + * Author: abin + * Date: 2023-03-22 + */ +@Data +@ApiModel("用户详情") +public class UserInfoResp { + + @ApiModelProperty(value = "用户id") + private Long id; + + @ApiModelProperty(value = "用户昵称") + private String name; + + @ApiModelProperty(value = "用户头像") + private String avatar; + + @ApiModelProperty(value = "性别 1为男性,2为女性") + private Integer sex; + + @ApiModelProperty(value = "剩余改名次数") + private Integer modifyNameChance; + +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/response/ws/WSBaseResp.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/response/ws/WSBaseResp.java new file mode 100644 index 0000000..65c4767 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/response/ws/WSBaseResp.java @@ -0,0 +1,18 @@ +package com.abin.mallchat.custom.user.domain.vo.response.ws; + +import lombok.Data; + +/** + * Description: ws的基本返回信息体 + * Author: abin + * Date: 2023-03-19 + */ +@Data +public class WSBaseResp { + /** + * ws推送给前端的消息 + * @see com.abin.mallchat.custom.user.domain.enums.WSRespTypeEnum + */ + private Integer type; + private T data; +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/response/ws/WSLoginSuccess.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/response/ws/WSLoginSuccess.java new file mode 100644 index 0000000..f874715 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/response/ws/WSLoginSuccess.java @@ -0,0 +1,23 @@ +package com.abin.mallchat.custom.user.domain.vo.response.ws; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +/** + * Description: + * Author: abin + * Date: 2023-03-19 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class WSLoginSuccess { + private Long uid; + private String avatar; + private String token; + private String name; +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/response/ws/WSLoginUrl.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/response/ws/WSLoginUrl.java new file mode 100644 index 0000000..433ccdf --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/response/ws/WSLoginUrl.java @@ -0,0 +1,20 @@ +package com.abin.mallchat.custom.user.domain.vo.response.ws; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +/** + * Description: + * Author: abin + * Date: 2023-03-19 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class WSLoginUrl { + private String loginUrl; +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/response/ws/WSMessage.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/response/ws/WSMessage.java new file mode 100644 index 0000000..9c7ba12 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/response/ws/WSMessage.java @@ -0,0 +1,21 @@ +package com.abin.mallchat.custom.user.domain.vo.response.ws; + +import com.abin.mallchat.custom.chat.domain.vo.response.ChatMemberResp; +import com.abin.mallchat.custom.chat.domain.vo.response.ChatMessageResp; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import java.util.ArrayList; +import java.util.List; + +/** + * Description: 用户消息推送 + * Author: abin + * Date: 2023-03-19 + */ +@Data +public class WSMessage extends ChatMessageResp { +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/response/ws/WSOnlineOfflineNotify.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/response/ws/WSOnlineOfflineNotify.java new file mode 100644 index 0000000..a5e9bbb --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/response/ws/WSOnlineOfflineNotify.java @@ -0,0 +1,28 @@ +package com.abin.mallchat.custom.user.domain.vo.response.ws; + +import com.abin.mallchat.custom.chat.domain.vo.response.ChatMemberResp; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + + +import java.util.ArrayList; +import java.util.List; + +/** + * Description:用户上下线变动的推送类 + * Author: abin + * Date: 2023-03-19 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class WSOnlineOfflineNotify { + private List changeList = new ArrayList<>();//新的上下线用户 + private Long onlineNum;//在线人数 + private Long totalNum;//总人数 + +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/LoginService.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/LoginService.java new file mode 100644 index 0000000..64c1fa1 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/LoginService.java @@ -0,0 +1,43 @@ +package com.abin.mallchat.custom.user.service; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +/** + * Description: 登录相关处理类 + * Author: abin + * Date: 2023-03-19 + */ +public interface LoginService { + + + /** + * 校验token是不是有效 + * + * @param token + * @return + */ + boolean verify(String token); + + /** + * 刷新token有效期 + * + * @param token + */ + void renewalTokenIfNecessary(String token); + + /** + * 登录成功,获取token + * + * @param uid + * @return 返回token + */ + String login(Long uid); + + /** + * 如果token有效,返回uid + * @param token + * @return + */ + Long getValidUid(String token); +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/UserService.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/UserService.java new file mode 100644 index 0000000..1150320 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/UserService.java @@ -0,0 +1,54 @@ +package com.abin.mallchat.custom.user.service; + +import com.abin.mallchat.common.user.domain.entity.User; +import com.abin.mallchat.custom.user.domain.vo.request.user.ModifyNameReq; +import com.abin.mallchat.custom.user.domain.vo.request.user.WearingBadgeReq; +import com.abin.mallchat.custom.user.domain.vo.response.user.BadgeResp; +import com.abin.mallchat.custom.user.domain.vo.response.user.UserInfoResp; +import com.baomidou.mybatisplus.extension.service.IService; + +import java.util.List; + +/** + *

+ * 用户表 服务类 + *

+ * + * @author abin + * @since 2023-03-19 + */ +public interface UserService { + + /** + * 获取前端展示信息 + * @param uid + * @return + */ + UserInfoResp getUserInfo(Long uid); + + /** + * 修改用户名 + * @param uid + * @param req + */ + void modifyName(Long uid, ModifyNameReq req); + + /** + * 用户徽章列表 + * @param uid + */ + List badges(Long uid); + + /** + * 佩戴徽章 + * @param uid + * @param req + */ + void wearingBadge(Long uid, WearingBadgeReq req); + + /** + * 用户注册 + * @param openId + */ + void register(String openId); +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/WebSocketService.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/WebSocketService.java new file mode 100644 index 0000000..2561d39 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/WebSocketService.java @@ -0,0 +1,63 @@ +package com.abin.mallchat.custom.user.service; + +import com.abin.mallchat.common.user.domain.entity.User; +import com.abin.mallchat.custom.user.domain.vo.request.ws.WSAuthorize; +import com.abin.mallchat.custom.user.domain.vo.response.ws.WSBaseResp; +import io.netty.channel.Channel; + +public interface WebSocketService { + /** + * 处理用户登录请求,需要返回一张带code的二维码 + * + * @param channel + */ + void handleLoginReq(Channel channel); + + /** + * 处理所有ws连接的事件 + * + * @param channel + */ + void connect(Channel channel); + + /** + * 处理ws断开连接的事件 + * + * @param channel + */ + void removed(Channel channel); + + /** + * 主动认证登录 + * + * @param channel + * @param wsAuthorize + */ + void authorize(Channel channel, WSAuthorize wsAuthorize); + + /** + * 扫码用户登录成功通知 + * + * @param loginCode + * @param user + * @param token + */ + Boolean scanLoginSuccess(Integer loginCode, User user, String token); + + /** + * 用户扫码成功 + * + * @param loginCode + */ + Boolean scanSuccess(Integer loginCode); + + /** + * 推动消息给所有在线的人 + * + * @param wsBaseResp 发送的消息体 + * @param skipUid 需要跳过的人 + */ + void sendToAllOnline(WSBaseResp wsBaseResp, Long skipUid); + + +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/WxMsgService.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/WxMsgService.java new file mode 100644 index 0000000..9d2c196 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/WxMsgService.java @@ -0,0 +1,107 @@ +package com.abin.mallchat.custom.user.service; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.abin.mallchat.common.common.config.ThreadPoolConfig; +import com.abin.mallchat.common.user.dao.UserDao; +import com.abin.mallchat.common.user.domain.entity.User; +import com.abin.mallchat.custom.user.service.adapter.TextBuilder; +import com.abin.mallchat.custom.user.service.adapter.UserAdapter; +import io.netty.channel.Channel; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.bean.WxOAuth2UserInfo; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Lazy; +import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.stereotype.Service; + +import java.net.URLEncoder; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArraySet; + +/** + * Description: 处理与微信api的交互逻辑 + * Author: abin + * Date: 2023-03-19 + */ +@Service +@Slf4j +public class WxMsgService { + /** + * 用户的openId和前端登录场景code的映射关系 + */ + private static final ConcurrentHashMap OPENID_EVENT_CODE_MAP = new ConcurrentHashMap<>(); + private static final String URL = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect"; + @Value("${wx.mp.callback}") + private String callback; + @Autowired + private UserDao userDao; + @Autowired + @Lazy + private WebSocketService webSocketService; + @Autowired + private LoginService loginService; + @Autowired + private UserService userService; + @Autowired + private ThreadPoolTaskExecutor threadPoolTaskExecutor; + + + public WxMpXmlOutMessage scan(WxMpService wxMpService, WxMpXmlMessage wxMpXmlMessage) { + String fromUser = wxMpXmlMessage.getFromUser(); + Integer eventKey = Integer.parseInt(getEventKey(wxMpXmlMessage)); + User user = userDao.getByOpenId(fromUser); + if (Objects.nonNull(user) && Objects.nonNull(user.getAvatar())) { + //注册且已经授权的用户,直接登录成功 + login(user.getId(), eventKey); + return null; + } + if (Objects.isNull(user)) { + //未注册的先注册 + userService.register(fromUser); + } + //保存openid和场景code的关系,后续才能通知到前端 + OPENID_EVENT_CODE_MAP.put(fromUser, eventKey); + //授权流程,给用户发送授权消息,并且异步通知前端扫码成功 + threadPoolTaskExecutor.execute(()->webSocketService.scanSuccess(eventKey)); + String skipUrl = String.format(URL, wxMpService.getWxMpConfigStorage().getAppId(), URLEncoder.encode(callback + "/wx/portal/public/callBack")); + WxMpXmlOutMessage.TEXT().build(); + return new TextBuilder().build("请点击链接授权:登录", wxMpXmlMessage, wxMpService); + } + + private String getEventKey(WxMpXmlMessage wxMpXmlMessage) { + //扫码关注的渠道事件有前缀,需要去除 + return wxMpXmlMessage.getEventKey().replace("qrscene_", ""); + } + + /** + * 用户授权 + * + * @param userInfo + */ + public void authorize(WxOAuth2UserInfo userInfo) { + User user = userDao.getByOpenId(userInfo.getOpenid()); + User update = UserAdapter.buildAuthorizeUser(user.getId(), userInfo); + //更新用户信息 + userDao.updateById(update); + //触发用户登录成功操作 + Integer eventKey = OPENID_EVENT_CODE_MAP.get(userInfo.getOpenid()); + login(user.getId(), eventKey); + } + + private void login(Long uid, Integer eventKey) { + User user = userDao.getById(uid); + //调用用户登录模块 + String token = loginService.login(uid); + //推送前端登录成功 + webSocketService.scanLoginSuccess(eventKey, user, token); + + } +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/adapter/AbstractBuilder.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/adapter/AbstractBuilder.java new file mode 100644 index 0000000..7d92567 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/adapter/AbstractBuilder.java @@ -0,0 +1,17 @@ +package com.abin.mallchat.custom.user.service.adapter; + +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Binary Wang + */ +public abstract class AbstractBuilder { + protected final Logger logger = LoggerFactory.getLogger(getClass()); + + public abstract WxMpXmlOutMessage build(String content, + WxMpXmlMessage wxMessage, WxMpService service); +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/adapter/ImageBuilder.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/adapter/ImageBuilder.java new file mode 100644 index 0000000..10484f3 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/adapter/ImageBuilder.java @@ -0,0 +1,24 @@ +package com.abin.mallchat.custom.user.service.adapter; + +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutImageMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; + +/** + * @author Binary Wang + */ +public class ImageBuilder extends AbstractBuilder { + + @Override + public WxMpXmlOutMessage build(String content, WxMpXmlMessage wxMessage, + WxMpService service) { + + WxMpXmlOutImageMessage m = WxMpXmlOutMessage.IMAGE().mediaId(content) + .fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser()) + .build(); + + return m; + } + +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/adapter/TextBuilder.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/adapter/TextBuilder.java new file mode 100644 index 0000000..b314885 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/adapter/TextBuilder.java @@ -0,0 +1,21 @@ +package com.abin.mallchat.custom.user.service.adapter; + +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutTextMessage; + +/** + * @author Binary Wang + */ +public class TextBuilder extends AbstractBuilder { + + @Override + public WxMpXmlOutMessage build(String content, WxMpXmlMessage wxMessage, + WxMpService service) { + WxMpXmlOutTextMessage m = WxMpXmlOutMessage.TEXT().content(content) + .fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser()) + .build(); + return m; + } +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/adapter/UserAdapter.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/adapter/UserAdapter.java new file mode 100644 index 0000000..e0c3a38 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/adapter/UserAdapter.java @@ -0,0 +1,59 @@ +package com.abin.mallchat.custom.user.service.adapter; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.ObjectUtil; +import com.abin.mallchat.common.common.domain.enums.YesOrNoEnum; +import com.abin.mallchat.common.user.domain.entity.ItemConfig; +import com.abin.mallchat.common.user.domain.entity.User; +import com.abin.mallchat.common.user.domain.entity.UserBackpack; +import com.abin.mallchat.custom.user.domain.vo.response.user.BadgeResp; +import com.abin.mallchat.custom.user.domain.vo.response.user.UserInfoResp; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.bean.WxOAuth2UserInfo; +import me.chanjar.weixin.mp.bean.result.WxMpUser; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Description: 用户适配器 + * Author: abin + * Date: 2023-03-19 + */ +@Slf4j +public class UserAdapter { + + public static User buildUser(String openId) { + User user = new User(); + user.setOpenId(openId); + return user; + } + + public static User buildAuthorizeUser(Long id, WxOAuth2UserInfo userInfo) { + User user = new User(); + user.setId(id); + user.setAvatar(userInfo.getHeadImgUrl()); + user.setName(userInfo.getNickname()); + user.setSex(userInfo.getSex()); + return user; + } + + public static UserInfoResp buildUserInfoResp(User userInfo, Integer countByValidItemId) { + UserInfoResp userInfoResp = new UserInfoResp(); + BeanUtil.copyProperties(userInfo, userInfoResp); + userInfoResp.setModifyNameChance(countByValidItemId); + return userInfoResp; + } + + public static List buildBadgeResp(List itemConfigs, List backpacks, User user) { + Set obtainItemSet = backpacks.stream().map(UserBackpack::getItemId).collect(Collectors.toSet()); + return itemConfigs.stream().map(a -> { + BadgeResp resp = new BadgeResp(); + BeanUtil.copyProperties(a, resp); + resp.setObtain(obtainItemSet.contains(a.getId()) ? YesOrNoEnum.YES.getStatus() : YesOrNoEnum.NO.getStatus()); + resp.setWearing(ObjectUtil.equal(a.getId(), user.getItemId()) ? YesOrNoEnum.YES.getStatus() : YesOrNoEnum.NO.getStatus()); + return resp; + }).collect(Collectors.toList()); + } +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/adapter/WSAdapter.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/adapter/WSAdapter.java new file mode 100644 index 0000000..339df3d --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/adapter/WSAdapter.java @@ -0,0 +1,112 @@ +package com.abin.mallchat.custom.user.service.adapter; + +import cn.hutool.core.bean.BeanUtil; +import com.abin.mallchat.common.user.domain.entity.User; +import com.abin.mallchat.common.user.domain.enums.ChatActiveStatusEnum; +import com.abin.mallchat.common.user.service.cache.UserCache; +import com.abin.mallchat.custom.chat.domain.vo.response.ChatMemberStatisticResp; +import com.abin.mallchat.custom.chat.domain.vo.response.ChatMessageResp; +import com.abin.mallchat.custom.chat.service.ChatService; +import com.abin.mallchat.custom.user.domain.enums.WSRespTypeEnum; +import com.abin.mallchat.custom.chat.domain.vo.response.ChatMemberResp; +import com.abin.mallchat.custom.user.domain.vo.response.ws.*; +import me.chanjar.weixin.mp.bean.result.WxMpQrCodeTicket; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.Collections; +import java.util.Date; + +/** + * Description: ws消息适配器 + * Author: abin + * Date: 2023-03-19 + */ +@Component +public class WSAdapter { + @Autowired + private ChatService chatService; + + public static WSBaseResp buildLoginResp(WxMpQrCodeTicket wxMpQrCodeTicket) { + WSBaseResp wsBaseResp = new WSBaseResp<>(); + wsBaseResp.setType(WSRespTypeEnum.LOGIN_URL.getType()); + wsBaseResp.setData(WSLoginUrl.builder().loginUrl(wxMpQrCodeTicket.getUrl()).build()); + return wsBaseResp; + } + + public static WSBaseResp buildLoginSuccessResp(User user, String token) { + WSBaseResp wsBaseResp = new WSBaseResp<>(); + wsBaseResp.setType(WSRespTypeEnum.LOGIN_SUCCESS.getType()); + WSLoginSuccess wsLoginSuccess = WSLoginSuccess.builder() + .avatar(user.getAvatar()) + .name(user.getName()) + .token(token) + .uid(user.getId()) + .build(); + wsBaseResp.setData(wsLoginSuccess); + return wsBaseResp; + } + + public static WSBaseResp buildScanSuccessResp() { + WSBaseResp wsBaseResp = new WSBaseResp(); + wsBaseResp.setType(WSRespTypeEnum.LOGIN_SCAN_SUCCESS.getType()); + return wsBaseResp; + } + + public WSBaseResp buildOnlineNotifyResp(User user) { + WSBaseResp wsBaseResp = new WSBaseResp<>(); + wsBaseResp.setType(WSRespTypeEnum.ONLINE_OFFLINE_NOTIFY.getType()); + WSOnlineOfflineNotify onlineOfflineNotify = new WSOnlineOfflineNotify(); + onlineOfflineNotify.setChangeList(Collections.singletonList(buildOnlineInfo(user))); + assembleNum(onlineOfflineNotify); + wsBaseResp.setData(onlineOfflineNotify); + return wsBaseResp; + } + + public WSBaseResp buildOfflineNotifyResp(User user) { + WSBaseResp wsBaseResp = new WSBaseResp<>(); + wsBaseResp.setType(WSRespTypeEnum.ONLINE_OFFLINE_NOTIFY.getType()); + WSOnlineOfflineNotify onlineOfflineNotify = new WSOnlineOfflineNotify(); + onlineOfflineNotify.setChangeList(Collections.singletonList(buildOfflineInfo(user))); + assembleNum(onlineOfflineNotify); + wsBaseResp.setData(onlineOfflineNotify); + return wsBaseResp; + } + + private void assembleNum(WSOnlineOfflineNotify onlineOfflineNotify) { + ChatMemberStatisticResp memberStatistic = chatService.getMemberStatistic(); + onlineOfflineNotify.setOnlineNum(memberStatistic.getOnlineNum()); + onlineOfflineNotify.setTotalNum(memberStatistic.getTotalNum()); + } + + private static ChatMemberResp buildOnlineInfo(User user) { + ChatMemberResp info = new ChatMemberResp(); + BeanUtil.copyProperties(user, info); + info.setUid(user.getId()); + info.setActiveStatus(ChatActiveStatusEnum.ONLINE.getStatus()); + info.setLastOptTime(user.getLastOptTime()); + return info; + } + + private static ChatMemberResp buildOfflineInfo(User user) { + ChatMemberResp info = new ChatMemberResp(); + BeanUtil.copyProperties(user, info); + info.setUid(user.getId()); + info.setActiveStatus(ChatActiveStatusEnum.OFFLINE.getStatus()); + info.setLastOptTime(user.getLastOptTime()); + return info; + } + + public static WSBaseResp buildInvalidateTokenResp() { + WSBaseResp wsBaseResp = new WSBaseResp<>(); + wsBaseResp.setType(WSRespTypeEnum.INVALIDATE_TOKEN.getType()); + return wsBaseResp; + } + + public static WSBaseResp buildMsgSend(ChatMessageResp msgResp) { + WSBaseResp wsBaseResp = new WSBaseResp<>(); + wsBaseResp.setType(WSRespTypeEnum.MESSAGE.getType()); + wsBaseResp.setData(msgResp); + return wsBaseResp; + } +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/handler/AbstractHandler.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/handler/AbstractHandler.java new file mode 100644 index 0000000..29a33b0 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/handler/AbstractHandler.java @@ -0,0 +1,12 @@ +package com.abin.mallchat.custom.user.service.handler; + +import me.chanjar.weixin.mp.api.WxMpMessageHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Binary Wang + */ +public abstract class AbstractHandler implements WxMpMessageHandler { + protected Logger logger = LoggerFactory.getLogger(getClass()); +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/handler/LogHandler.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/handler/LogHandler.java new file mode 100644 index 0000000..f4959ab --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/handler/LogHandler.java @@ -0,0 +1,27 @@ +package com.abin.mallchat.custom.user.service.handler; + +import cn.hutool.json.JSONUtil; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; +import org.springframework.stereotype.Component; + +import java.util.Map; + +/** + * @author Binary Wang + */ +@Component +@Slf4j +public class LogHandler extends AbstractHandler { + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, + Map context, WxMpService wxMpService, + WxSessionManager sessionManager) { + log.info("\n接收到请求消息,内容:{}", JSONUtil.toJsonStr(wxMessage)); + return null; + } + +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/handler/MsgHandler.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/handler/MsgHandler.java new file mode 100644 index 0000000..ecad5ea --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/handler/MsgHandler.java @@ -0,0 +1,52 @@ +package com.abin.mallchat.custom.user.service.handler; + +import cn.hutool.json.JSONUtil; +import com.abin.mallchat.custom.user.service.adapter.TextBuilder; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; + +import java.util.Map; + +import static me.chanjar.weixin.common.api.WxConsts.XmlMsgType; + +/** + * @author Binary Wang + */ +@Component +public class MsgHandler extends AbstractHandler { + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, + Map context, WxMpService weixinService, + WxSessionManager sessionManager) { + + if (!wxMessage.getMsgType().equals(XmlMsgType.EVENT)) { + //可以选择将消息保存到本地 + } + + //当用户输入关键词如“你好”,“客服”等,并且有客服在线时,把消息转发给在线客服 + try { + if (StringUtils.startsWithAny(wxMessage.getContent(), "你好", "客服") + && weixinService.getKefuService().kfOnlineList() + .getKfOnlineList().size() > 0) { + return WxMpXmlOutMessage.TRANSFER_CUSTOMER_SERVICE() + .fromUser(wxMessage.getToUser()) + .toUser(wxMessage.getFromUser()).build(); + } + } catch (WxErrorException e) { + e.printStackTrace(); + } + + //组装回复消息 + String content = "收到信息内容:" + JSONUtil.toJsonStr(wxMessage); + + return new TextBuilder().build(content, wxMessage, weixinService); + + } + +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/handler/ScanHandler.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/handler/ScanHandler.java new file mode 100644 index 0000000..1b3efd3 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/handler/ScanHandler.java @@ -0,0 +1,40 @@ +package com.abin.mallchat.custom.user.service.handler; + +import cn.hutool.core.util.URLUtil; +import com.abin.mallchat.common.user.dao.UserDao; +import com.abin.mallchat.common.user.domain.entity.User; +import com.abin.mallchat.custom.user.service.LoginService; +import com.abin.mallchat.custom.user.service.WebSocketService; +import com.abin.mallchat.custom.user.service.WxMsgService; +import com.abin.mallchat.custom.user.service.adapter.TextBuilder; +import com.abin.mallchat.custom.user.service.adapter.UserAdapter; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; +import me.chanjar.weixin.mp.bean.result.WxMpUser; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.net.URLEncoder; +import java.util.Map; +import java.util.Objects; + +@Component +public class ScanHandler extends AbstractHandler { + + + @Autowired + private WxMsgService wxMsgService; + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMpXmlMessage, Map map, + WxMpService wxMpService, WxSessionManager wxSessionManager) throws WxErrorException { + // 扫码事件处理 + return wxMsgService.scan(wxMpService,wxMpXmlMessage); + + + } + +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/handler/SubscribeHandler.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/handler/SubscribeHandler.java new file mode 100644 index 0000000..9a1a875 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/handler/SubscribeHandler.java @@ -0,0 +1,59 @@ +package com.abin.mallchat.custom.user.service.handler; + +import com.abin.mallchat.custom.user.service.WxMsgService; +import com.abin.mallchat.custom.user.service.adapter.TextBuilder; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; +import me.chanjar.weixin.mp.bean.result.WxMpUser; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.Map; + +/** + * @author Binary Wang + */ +@Component +public class SubscribeHandler extends AbstractHandler { + @Autowired + private WxMsgService wxMsgService; + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, + Map context, WxMpService weixinService, + WxSessionManager sessionManager) throws WxErrorException { + + this.logger.info("新关注用户 OPENID: " + wxMessage.getFromUser()); + + WxMpXmlOutMessage responseResult = null; + try { + responseResult = this.handleSpecial(weixinService, wxMessage); + } catch (Exception e) { + this.logger.error(e.getMessage(), e); + } + + if (responseResult != null) { + return responseResult; + } + + try { + return new TextBuilder().build("感谢关注", wxMessage, weixinService); + } catch (Exception e) { + this.logger.error(e.getMessage(), e); + } + + return null; + } + + /** + * 处理特殊请求,比如如果是扫码进来的,可以做相应处理 + */ + private WxMpXmlOutMessage handleSpecial(WxMpService weixinService, WxMpXmlMessage wxMessage) + throws Exception { + return wxMsgService.scan(weixinService, wxMessage); + } + +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/impl/LoginServiceImpl.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/impl/LoginServiceImpl.java new file mode 100644 index 0000000..2bf3035 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/impl/LoginServiceImpl.java @@ -0,0 +1,82 @@ +package com.abin.mallchat.custom.user.service.impl; + +import cn.hutool.core.util.StrUtil; +import com.abin.mallchat.common.common.constant.RedisKey; +import com.abin.mallchat.common.common.utils.JwtUtils; +import com.abin.mallchat.common.common.utils.RedisUtils; +import com.abin.mallchat.custom.user.service.LoginService; +import com.auth0.jwt.interfaces.Claim; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +/** + * Description: 登录相关处理类 + * Author: abin + * Date: 2023-03-19 + */ +@Service +@Slf4j +public class LoginServiceImpl implements LoginService { + + @Autowired + private JwtUtils jwtUtils; + @Autowired + private RedisUtils redisUtils; + //token过期时间 + private static final Integer TOKEN_EXPIRE_DAYS = 5; + //token续期时间 + private static final Integer TOKEN_RENEWAL_DAYS = 2; + + /** + * 校验token是不是有效 + * + * @param token + * @return + */ + public boolean verify(String token) { + Long uid = jwtUtils.getUidOrNull(token); + if (Objects.isNull(uid)) { + return false; + } + String key = RedisKey.getKey(RedisKey.USER_TOKEN_STRING, uid); + String realToken = redisUtils.getStr(key); + return token.equals(realToken);//有可能token失效了,需要校验是不是和最新token一致 + } + + @Async + public void renewalTokenIfNecessary(String token) { + Long uid = jwtUtils.getUidOrNull(token); + if (Objects.isNull(uid)) { + return; + } + String key = RedisKey.getKey(RedisKey.USER_TOKEN_STRING, uid); + long expireDays = redisUtils.getExpire(key, TimeUnit.DAYS); + if (expireDays == -2) {//不存在的key + return; + } + if (expireDays < TOKEN_RENEWAL_DAYS) {//小于一天的token帮忙续期 + redisUtils.expire(key, TOKEN_EXPIRE_DAYS, TimeUnit.DAYS); + } + } + + @Override + public String login(Long uid) { + //获取用户token + String token = jwtUtils.createToken(uid); + //存储到redis + String key = RedisKey.getKey(RedisKey.USER_TOKEN_STRING, uid); + redisUtils.set(key, token, TOKEN_EXPIRE_DAYS, TimeUnit.DAYS);//token过期用redis中心化控制,初期采用5天过期,剩1天自动续期的方案。后续可以用双token实现 + return token; + } + + @Override + public Long getValidUid(String token) { + return verify(token) ? jwtUtils.getUidOrNull(token) : null; + } +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/impl/UserServiceImpl.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/impl/UserServiceImpl.java new file mode 100644 index 0000000..d39dc85 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/impl/UserServiceImpl.java @@ -0,0 +1,108 @@ +package com.abin.mallchat.custom.user.service.impl; + +import com.abin.mallchat.common.common.domain.enums.IdempotentEnum; +import com.abin.mallchat.common.common.utils.AssertUtil; +import com.abin.mallchat.common.user.dao.ItemConfigDao; +import com.abin.mallchat.common.user.dao.UserBackpackDao; +import com.abin.mallchat.common.user.dao.UserDao; +import com.abin.mallchat.common.user.domain.entity.ItemConfig; +import com.abin.mallchat.common.user.domain.entity.User; +import com.abin.mallchat.common.user.domain.entity.UserBackpack; +import com.abin.mallchat.common.user.domain.enums.ItemEnum; +import com.abin.mallchat.common.user.domain.enums.ItemTypeEnum; +import com.abin.mallchat.common.user.service.IUserBackpackService; +import com.abin.mallchat.common.user.service.cache.ItemCache; +import com.abin.mallchat.common.user.service.cache.UserCache; +import com.abin.mallchat.custom.common.event.UserRegisterEvent; +import com.abin.mallchat.custom.user.domain.vo.request.user.ModifyNameReq; +import com.abin.mallchat.custom.user.domain.vo.request.user.WearingBadgeReq; +import com.abin.mallchat.custom.user.domain.vo.response.user.BadgeResp; +import com.abin.mallchat.custom.user.domain.vo.response.user.UserInfoResp; +import com.abin.mallchat.custom.user.service.UserService; +import com.abin.mallchat.custom.user.service.adapter.UserAdapter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * Description: 用户基础操作类 + * Author: abin + * Date: 2023-03-19 + */ +@Service +@Slf4j +public class UserServiceImpl implements UserService { + + @Autowired + private UserCache userCache; + @Autowired + private UserBackpackDao userBackpackDao; + @Autowired + private UserDao userDao; + @Autowired + private ItemConfigDao itemConfigDao; + @Autowired + private IUserBackpackService iUserBackpackService; + @Autowired + private ApplicationEventPublisher applicationEventPublisher; + @Autowired + private ItemCache itemCache; + + @Override + public UserInfoResp getUserInfo(Long uid) { + User userInfo = userCache.getUserInfo(uid); + Integer countByValidItemId = userBackpackDao.getCountByValidItemId(uid, ItemEnum.MODIFY_NAME_CARD.getId()); + return UserAdapter.buildUserInfoResp(userInfo, countByValidItemId); + } + + @Override + @Transactional + public void modifyName(Long uid, ModifyNameReq req) { + //判断改名卡够不够 + UserBackpack firstValidItem = userBackpackDao.getFirstValidItem(uid, ItemEnum.MODIFY_NAME_CARD.getId()); + AssertUtil.isNotEmpty(firstValidItem, "改名次数不够了,等后续活动送改名卡哦"); + //使用改名卡 + boolean useSuccess = userBackpackDao.invalidItem(firstValidItem.getItemId()); + if (useSuccess) {//用乐观锁,就不用分布式锁了 + //改名 + userDao.modifyName(uid, req.getName()); + } + } + + @Override + public List badges(Long uid) { + //查询所有徽章 + List itemConfigs =itemCache.getByType(ItemTypeEnum.BADGE.getType()); + //查询用户拥有的徽章 + List backpacks = userBackpackDao.getByItemIds(uid, itemConfigs.stream().map(ItemConfig::getId).collect(Collectors.toList())); + //查询用户当前佩戴的标签 + User user = userDao.getById(uid); + return UserAdapter.buildBadgeResp(itemConfigs, backpacks, user); + } + + @Override + @Transactional + public void wearingBadge(Long uid, WearingBadgeReq req) { + //确保有这个徽章 + UserBackpack firstValidItem = userBackpackDao.getFirstValidItem(uid, req.getBadgeId()); + AssertUtil.isNotEmpty(firstValidItem, "您没有这个徽章哦,快去达成条件获取吧"); + //确保物品类型是徽章 + ItemConfig itemConfig = itemConfigDao.getById(firstValidItem.getItemId()); + AssertUtil.equal(itemConfig.getType(), ItemTypeEnum.BADGE.getType(), "该徽章不可佩戴"); + //佩戴徽章 + userDao.wearingBadge(uid, req.getBadgeId()); + } + + @Override + @Transactional + public void register(String openId) { + User insert = User.builder().openId(openId).build(); + userDao.save(insert); + applicationEventPublisher.publishEvent(new UserRegisterEvent(this,insert)); + } +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/impl/WebSocketServiceImpl.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/impl/WebSocketServiceImpl.java new file mode 100644 index 0000000..b1dbf49 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/impl/WebSocketServiceImpl.java @@ -0,0 +1,244 @@ +package com.abin.mallchat.custom.user.service.impl; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.json.JSONUtil; +import com.abin.mallchat.common.common.config.ThreadPoolConfig; +import com.abin.mallchat.common.user.dao.UserDao; +import com.abin.mallchat.common.user.domain.entity.User; +import com.abin.mallchat.custom.common.event.UserOfflineEvent; +import com.abin.mallchat.custom.user.domain.dto.ws.WSChannelExtraDTO; +import com.abin.mallchat.custom.user.domain.vo.request.ws.WSAuthorize; +import com.abin.mallchat.custom.user.domain.vo.response.ws.WSBaseResp; +import com.abin.mallchat.custom.common.event.UserOnlineEvent; +import com.abin.mallchat.custom.user.service.LoginService; +import com.abin.mallchat.custom.user.service.WebSocketService; +import com.abin.mallchat.custom.user.service.adapter.WSAdapter; +import com.abin.mallchat.custom.user.websocket.NettyUtil; +import io.netty.channel.Channel; +import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.result.WxMpQrCodeTicket; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.stereotype.Component; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Description: websocket处理类 + * Author: abin + * Date: 2023-03-19 16:21 + */ +@Component +@Slf4j +public class WebSocketServiceImpl implements WebSocketService { + + + /** + * 所有请求登录的code与channel关系 + * todo 有可能有人请求了二维码,就是不登录,留个坑,之后处理 + */ + private static final ConcurrentHashMap WAIT_LOGIN_MAP = new ConcurrentHashMap<>(); + /** + * 所有已连接的websocket连接列表和一些额外参数 + */ + private static final ConcurrentHashMap ONLINE_WS_MAP = new ConcurrentHashMap<>(); + + public static final int EXPIRE_SECONDS = 60 * 60; + @Autowired + private WxMpService wxMpService; + @Autowired + private LoginService loginService; + @Autowired + private UserDao userDao; + @Autowired + private ApplicationEventPublisher applicationEventPublisher; + @Autowired + @Qualifier(ThreadPoolConfig.WS_EXECUTOR) + private ThreadPoolTaskExecutor threadPoolTaskExecutor; + + /** + * 处理用户登录请求,需要返回一张带code的二维码 + * + * @param channel + */ + @SneakyThrows + @Override + public void handleLoginReq(Channel channel) { + //生成随机不重复的登录码 + Integer code = generateLoginCode(channel); + //请求微信接口,获取登录码地址 + WxMpQrCodeTicket wxMpQrCodeTicket = wxMpService.getQrcodeService().qrCodeCreateTmpTicket(code, EXPIRE_SECONDS); + //返回给前端 + sendMsg(channel, WSAdapter.buildLoginResp(wxMpQrCodeTicket)); + } + + /** + * 获取不重复的登录的code,微信要求最大不超过int的存储极限 + * 防止并发,可以给方法加上synchronize,也可以使用cas乐观锁 + * + * @return + */ + private Integer generateLoginCode(Channel channel) { + int code; + do { + code = RandomUtil.randomInt(Integer.MAX_VALUE); + } while (WAIT_LOGIN_MAP.contains(code) + || Objects.nonNull(WAIT_LOGIN_MAP.putIfAbsent(code, channel))); + return code; + } + + /** + * 处理所有ws连接的事件 + * + * @param channel + */ + @Override + public void connect(Channel channel) { + ONLINE_WS_MAP.put(channel, new WSChannelExtraDTO()); + } + + @Override + public void removed(Channel channel) { + WSChannelExtraDTO wsChannelExtraDTO = ONLINE_WS_MAP.get(channel); + Optional uidOptional = Optional.ofNullable(wsChannelExtraDTO) + .map(WSChannelExtraDTO::getUid); + ONLINE_WS_MAP.remove(channel); + if (uidOptional.isPresent()) {//已登录用户断连,需要下线通知 + User user = new User(); + user.setId(uidOptional.get()); + user.setLastOptTime(new Date()); + applicationEventPublisher.publishEvent(new UserOfflineEvent(this, user)); + } + } + + @Override + public void authorize(Channel channel, WSAuthorize wsAuthorize) { + //校验token + boolean verifySuccess = loginService.verify(wsAuthorize.getToken()); + if (verifySuccess) {//用户校验成功给用户登录 + User user = userDao.getById(loginService.getValidUid(wsAuthorize.getToken())); + loginSuccess(channel, user, wsAuthorize.getToken()); + } else { //让前端的token失效 + sendMsg(channel, WSAdapter.buildInvalidateTokenResp()); + } + } + + /** + * 登录成功,并更新状态 + */ + private void loginSuccess(Channel channel, User user, String token) { + //更新上线列表 + online(channel, user.getId()); + //返回给用户登录成功 + sendMsg(channel, WSAdapter.buildLoginSuccessResp(user, token)); + //发送用户上线事件 + user.setLastOptTime(new Date()); + user.getIpInfo().refreshIp(NettyUtil.getAttr(channel, NettyUtil.IP)); + applicationEventPublisher.publishEvent(new UserOnlineEvent(this, user)); + } + + + /** + * 用户上线 + */ + private void online(Channel channel, Long uid) { + getOrInitChannelExt(channel).setUid(uid); + } + + + @Override + public Boolean scanLoginSuccess(Integer loginCode, User user, String token) { + //发送消息 + Channel channel = WAIT_LOGIN_MAP.get(loginCode); + if (Objects.isNull(channel)) { + return Boolean.FALSE; + } + //移除code + WAIT_LOGIN_MAP.remove(loginCode); + //用户登录 + loginSuccess(channel, user, token); + return true; + } + + @Override + public Boolean scanSuccess(Integer loginCode) { + Channel channel = WAIT_LOGIN_MAP.get(loginCode); + if (Objects.isNull(channel)) { + return Boolean.FALSE; + } + sendMsg(channel, WSAdapter.buildScanSuccessResp()); + return true; + } + + + /** + * 如果在线列表不存在,就先把该channel放进在线列表 + * + * @param channel + * @return + */ + private WSChannelExtraDTO getOrInitChannelExt(Channel channel) { + WSChannelExtraDTO wsChannelExtraDTO = + ONLINE_WS_MAP.getOrDefault(channel, new WSChannelExtraDTO()); + WSChannelExtraDTO old = ONLINE_WS_MAP.putIfAbsent(channel, wsChannelExtraDTO); + return ObjectUtil.isNull(old) ? wsChannelExtraDTO : old; + } + + //entrySet的值不是快照数据,但是它支持遍历,所以无所谓了,不用快照也行。 + @Override + public void sendToAllOnline(WSBaseResp wsBaseResp, Long skipUid) { + ONLINE_WS_MAP.forEach((channel, ext) -> { + if (ObjectUtil.equal(ext.getUid(), skipUid)) { + return; + } + threadPoolTaskExecutor.execute(() -> sendMsg(channel, wsBaseResp)); + }); + } + + private void sendMsg(Channel channel, WSBaseResp wsBaseResp) { + channel.writeAndFlush(new TextWebSocketFrame(JSONUtil.toJsonStr(wsBaseResp))); + } + + /** + * 案例证明ConcurrentHashMap#entrySet的值不是快照数据 + * + * @param args + * @throws InterruptedException + */ + public static void main(String[] args) throws InterruptedException { + ReentrantLock reentrantLock = new ReentrantLock(); + Condition condition = reentrantLock.newCondition(); + ConcurrentHashMap a = new ConcurrentHashMap<>(); + a.put(1, 1); + a.put(2, 2); + new Thread(() -> { + reentrantLock.lock(); + Set> entries = a.entrySet(); + System.out.println(entries); + try { + condition.await(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + System.out.println(entries); + reentrantLock.unlock(); + + }).start(); + Thread.sleep(1000); + reentrantLock.lock(); + a.put(3, 3); + System.out.println("haha"); + condition.signalAll(); + reentrantLock.unlock(); + Thread.sleep(1000); + } +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/websocket/HttpHeadersHandler.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/websocket/HttpHeadersHandler.java new file mode 100644 index 0000000..33460c0 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/websocket/HttpHeadersHandler.java @@ -0,0 +1,29 @@ +package com.abin.mallchat.custom.user.websocket; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.HttpHeaders; +import io.netty.util.Attribute; +import io.netty.util.AttributeKey; + +import java.net.InetSocketAddress; +import java.util.Objects; + +public class HttpHeadersHandler extends ChannelInboundHandlerAdapter { + private AttributeKey key = AttributeKey.valueOf("Id"); + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + if (msg instanceof FullHttpRequest) { + HttpHeaders headers = ((FullHttpRequest) msg).headers(); + String ip = headers.get("X-Real-IP"); + if (Objects.isNull(ip)) {//如果没经过nginx,就直接获取远端地址 + InetSocketAddress address = (InetSocketAddress) ctx.channel().remoteAddress(); + ip = address.getAddress().getHostAddress(); + } + NettyUtil.setAttr(ctx.channel(), NettyUtil.IP, ip); + } + ctx.fireChannelRead(msg); + } +} \ No newline at end of file diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/websocket/NettyUtil.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/websocket/NettyUtil.java new file mode 100644 index 0000000..d10a562 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/websocket/NettyUtil.java @@ -0,0 +1,27 @@ +package com.abin.mallchat.custom.user.websocket; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.util.Attribute; +import io.netty.util.AttributeKey; +import org.omg.CORBA.PUBLIC_MEMBER; + +/** + * Description: netty工具类 + * Author: abin + * Date: 2023-04-18 + */ + +public class NettyUtil { + + public static AttributeKey IP = AttributeKey.valueOf("ip"); + + public static void setAttr(Channel channel, AttributeKey attributeKey, T data) { + Attribute attr = channel.attr(attributeKey); + attr.set(data); + } + + public static T getAttr(Channel channel, AttributeKey ip) { + return channel.attr(ip).get(); + } +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/websocket/NettyWebSocketServer.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/websocket/NettyWebSocketServer.java new file mode 100644 index 0000000..eda6742 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/websocket/NettyWebSocketServer.java @@ -0,0 +1,100 @@ +package com.abin.mallchat.custom.user.websocket; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.*; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpServerCodec; +import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; +import io.netty.handler.logging.LogLevel; +import io.netty.handler.logging.LoggingHandler; +import io.netty.handler.stream.ChunkedWriteHandler; +import io.netty.handler.timeout.IdleStateHandler; +import io.netty.util.concurrent.Future; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import java.net.InetSocketAddress; + +@Slf4j +@Configuration +public class NettyWebSocketServer { + public static final int WEB_SOCKET_PORT = 8090; + // 创建线程池执行器 + private EventLoopGroup bossGroup = new NioEventLoopGroup(1); + private EventLoopGroup workerGroup = new NioEventLoopGroup(8); + + /** + * 启动 ws server + * + * @return + * @throws InterruptedException + */ + @PostConstruct + public void start() throws InterruptedException { + run(); + } + + /** + * 销毁 + */ + @PreDestroy + public void destroy() throws InterruptedException { + Future future = bossGroup.shutdownGracefully(); + Future future1 = workerGroup.shutdownGracefully(); + future.sync(); + future1.sync(); + log.info("关闭 ws server 成功"); + } + + public void run() throws InterruptedException { + // 服务器启动引导对象 + ServerBootstrap serverBootstrap = new ServerBootstrap(); + serverBootstrap.group(bossGroup, workerGroup) + .channel(NioServerSocketChannel.class) + .option(ChannelOption.SO_BACKLOG, 128) + .option(ChannelOption.SO_KEEPALIVE, true) + .handler(new LoggingHandler(LogLevel.INFO)) // 为 bossGroup 添加 日志处理器 + .childHandler(new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel socketChannel) throws Exception { + ChannelPipeline pipeline = socketChannel.pipeline(); + //30秒客户端没有向服务器发送心跳则关闭连接 + pipeline.addLast(new IdleStateHandler(30, 0, 0)); + // 因为使用http协议,所以需要使用http的编码器,解码器 + pipeline.addLast(new HttpServerCodec()); + // 以块方式写,添加 chunkedWriter 处理器 + pipeline.addLast(new ChunkedWriteHandler()); + /** + * 说明: + * 1. http数据在传输过程中是分段的,HttpObjectAggregator可以把多个段聚合起来; + * 2. 这就是为什么当浏览器发送大量数据时,就会发出多次 http请求的原因 + */ + pipeline.addLast(new HttpObjectAggregator(8192)); + pipeline.addLast(new HttpHeadersHandler()); + /** + * 说明: + * 1. 对于 WebSocket,它的数据是以帧frame 的形式传递的; + * 2. 可以看到 WebSocketFrame 下面有6个子类 + * 3. 浏览器发送请求时: ws://localhost:7000/hello 表示请求的uri + * 4. WebSocketServerProtocolHandler 核心功能是把 http协议升级为 ws 协议,保持长连接; + * 是通过一个状态码 101 来切换的 + */ + pipeline.addLast(new WebSocketServerProtocolHandler("/")); + // 自定义handler ,处理业务逻辑 + pipeline.addLast(new NettyWebSocketServerHandler()); + } + }); + // 启动服务器,监听端口,阻塞直到启动成功 + serverBootstrap.bind(WEB_SOCKET_PORT).sync(); + System.out.println("启动成功"); + } + +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/websocket/NettyWebSocketServerHandler.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/websocket/NettyWebSocketServerHandler.java new file mode 100644 index 0000000..912b92e --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/websocket/NettyWebSocketServerHandler.java @@ -0,0 +1,104 @@ +package com.abin.mallchat.custom.user.websocket; + +import cn.hutool.extra.spring.SpringUtil; +import cn.hutool.json.JSONUtil; +import com.abin.mallchat.custom.user.domain.enums.WSReqTypeEnum; +import com.abin.mallchat.custom.user.domain.vo.request.ws.WSAuthorize; +import com.abin.mallchat.custom.user.domain.vo.request.ws.WSBaseReq; +import com.abin.mallchat.custom.user.service.WebSocketService; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; +import io.netty.handler.timeout.IdleState; +import io.netty.handler.timeout.IdleStateEvent; +import lombok.extern.slf4j.Slf4j; + +import java.net.SocketAddress; +import java.util.HashSet; +import java.util.Set; + + +@Slf4j +public class NettyWebSocketServerHandler extends SimpleChannelInboundHandler { + + // 当web客户端连接后,触发该方法 + @Override + public void handlerAdded(ChannelHandlerContext ctx) throws Exception { + getService().connect(ctx.channel()); + } + + // 客户端离线 + @Override + public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { + userOffLine(ctx); + } + + /** + * 取消绑定 + * + * @param ctx + * @throws Exception + */ + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + // 可能出现业务判断离线后再次触发 channelInactive + log.warn("触发 channelInactive 掉线![{}]", ctx.channel().id()); + userOffLine(ctx); + } + + private void userOffLine(ChannelHandlerContext ctx) { + getService().removed(ctx.channel()); + ctx.channel().close(); + } + + /** + * 心跳检查 + * + * @param ctx + * @param evt + * @throws Exception + */ + @Override + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { + if (evt instanceof IdleStateEvent) { + IdleStateEvent idleStateEvent = (IdleStateEvent) evt; + // 读空闲 + if (idleStateEvent.state() == IdleState.READER_IDLE) { + // 关闭用户的连接 + userOffLine(ctx); + } + } + super.userEventTriggered(ctx, evt); + } + + // 处理异常 + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + log.warn("异常发生,异常消息 ={}" + cause.getMessage()); + ctx.channel().close(); + } + + private WebSocketService getService() { + return SpringUtil.getBean(WebSocketService.class); + } + + // 读取客户端发送的请求报文 + @Override + protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception { + log.info("服务器端收到消息 = " + msg.text()); + WSBaseReq wsBaseReq = JSONUtil.toBean(msg.text(), WSBaseReq.class); + WSReqTypeEnum wsReqTypeEnum = WSReqTypeEnum.of(wsBaseReq.getType()); + switch (wsReqTypeEnum) { + case LOGIN: + getService().handleLoginReq(ctx.channel()); + break; + case HEARTBEAT: + break; + case AUTHORIZE: + getService().authorize(ctx.channel(), JSONUtil.toBean(wsBaseReq.getData(), WSAuthorize.class)); + default: + log.info("未知类型"); + } + } +} diff --git a/mallchat-custom-server/src/main/resources/logback.xml b/mallchat-custom-server/src/main/resources/logback.xml new file mode 100644 index 0000000..e7bb55a --- /dev/null +++ b/mallchat-custom-server/src/main/resources/logback.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + ${CONSOLE_LOG_PATTERN} + + + + + ${LOG_PATH}/${LOG_FILE}.log + true + + ${CONSOLE_LOG_PATTERN} + + + + ${LOG_PATH}/archived/${LOG_FILE}.%d{dd-MM-yyyy}.log + + 30 + 5GB + + + + + + + ERROR + ACCEPT + DENY + + ${LOG_PATH}/${LOG_FILE}.error.log + true + + ${CONSOLE_LOG_PATTERN} + + + + ${LOG_PATH}/archived/${LOG_FILE}.%d{dd-MM-yyyy}.error.log + + 30 + 2GB + + + + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..5e36959 --- /dev/null +++ b/pom.xml @@ -0,0 +1,121 @@ + + + 4.0.0 + + com.abin.mallchat + mallchat + 1.0-SNAPSHOT + + mallchat-common + mallchat-custom-server + + pom + + + org.springframework.boot + spring-boot-starter-parent + 2.6.7 + + + + + UTF-8 + UTF-8 + 1.8 + true + http://192.168.3.101:2375 + 0.40.2 + 5.8.18 + 3.0.0 + 1.6.0 + 1.6.0 + 3.4.1 + 3.5.10 + 8.0.29 + 2.7.5 + 0.9.1 + 2.5.0 + 7.2 + 8.4.5 + 2.3.1 + 1.0-SNAPSHOT + 1.18.10 + 4.1.76.Final + 4.4.0 + 3.4.0 + + + + + + com.abin.mallchat + mallchat-common + ${mallchat-common.version} + + + com.baomidou + mybatis-plus-boot-starter + ${mybatis-plus-boot-starter.version} + + + org.projectlombok + lombok + ${lombok.version} + + + com.github.binarywang + weixin-java-mp + ${weixin-java-mp.version} + + + + io.netty + netty-all + ${netty-all.version} + + + cn.hutool + hutool-all + ${hutool.version} + + + io.swagger + swagger-annotations + ${swagger-annotations.version} + + + + org.mybatis + mybatis + ${mybatis.version} + + + com.baomidou + mybatis-plus-generator + ${mybatis-plus-generator.version} + + + + mysql + mysql-connector-java + ${mysql-connector.version} + + + + io.jsonwebtoken + jjwt + ${jjwt.version} + + + + com.aliyun.oss + aliyun-sdk-oss + ${aliyun-oss.version} + + + + + + \ No newline at end of file