From ff32b015abfe91fcb11a72b52d88f05717d81868 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Fri, 16 May 2025 20:35:08 +0800 Subject: [PATCH] reactor:移除 mp 的 api 包 --- yudao-module-mp/pom.xml | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------- yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/MpAccountController.java | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountBaseVO.java | 43 +++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountCreateReqVO.java | 14 ++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountPageReqVO.java | 24 ++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountRespVO.java | 25 +++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountSimpleRespVO.java | 16 ++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountUpdateReqVO.java | 20 ++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/MpMaterialController.http | 5 +++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/MpMaterialController.java | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialPageReqVO.java | 27 +++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialRespVO.java | 47 +++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialUploadNewsImageReqVO.java | 23 +++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialUploadPermanentReqVO.java | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialUploadRespVO.java | 16 ++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialUploadTemporaryReqVO.java | 28 ++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/MpMenuController.http | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/MpMenuController.java | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/vo/MpMenuBaseVO.java | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/vo/MpMenuRespVO.java | 28 ++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/vo/MpMenuSaveReqVO.java | 34 ++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpAutoReplyController.http | 5 +++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpAutoReplyController.java | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpMessageController.http | 33 +++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpMessageController.java | 47 +++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyBaseVO.java | 109 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyCreateReqVO.java | 20 ++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyPageReqVO.java | 21 +++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyRespVO.java | 27 +++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyUpdateReqVO.java | 20 ++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/message/MpMessagePageReqVO.java | 35 +++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/message/MpMessageRespVO.java | 101 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/message/MpMessageSendReqVO.java | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/MpDraftController.http | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/MpDraftController.java | 136 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/MpFreePublishController.http | 13 +++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/MpFreePublishController.java | 119 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/vo/MpDraftPageReqVO.java | 21 +++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/vo/MpFreePublishPageReqVO.java | 21 +++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/open/MpOpenController.java | 117 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/open/vo/MpOpenCheckSignatureReqVO.java | 29 +++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/open/vo/MpOpenHandleMessageReqVO.java | 37 +++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/MpStatisticsController.java | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsGetReqVO.java | 25 +++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsInterfaceSummaryRespVO.java | 27 +++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsUpstreamMessageRespVO.java | 21 +++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsUserCumulateRespVO.java | 18 ++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsUserSummaryRespVO.java | 24 ++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/MpTagController.http | 39 +++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/MpTagController.java | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagBaseVO.java | 21 +++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagCreateReqVO.java | 20 ++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagPageReqVO.java | 24 ++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagRespVO.java | 25 +++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagSimpleRespVO.java | 19 +++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagUpdateReqVO.java | 20 ++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/MpUserController.http | 18 ++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/MpUserController.java | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/vo/MpUserPageReqVO.java | 30 ++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/vo/MpUserRespVO.java | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/vo/MpUserUpdateReqVO.java | 26 ++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/package-info.java | 6 ++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/convert/account/MpAccountConvert.java | 31 +++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/convert/material/MpMaterialConvert.java | 47 +++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/convert/menu/MpMenuConvert.java | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/convert/message/MpAutoReplyConvert.java | 37 +++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/convert/message/MpMessageConvert.java | 172 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/convert/statistics/MpStatisticsConvert.java | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/convert/tag/MpTagConvert.java | 44 ++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/convert/user/MpUserConvert.java | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/account/MpAccountDO.java | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/material/MpMaterialDO.java | 99 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/menu/MpMenuDO.java | 185 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/message/MpAutoReplyDO.java | 165 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/message/MpMessageDO.java | 242 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/tag/MpTagDO.java | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/user/MpUserDO.java | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/account/MpAccountMapper.java | 31 +++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/material/MpMaterialMapper.java | 33 +++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/menu/MpMenuMapper.java | 25 +++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/message/MpAutoReplyMapper.java | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/message/MpMessageMapper.java | 22 ++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/tag/MpTagMapper.java | 26 ++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/user/MpUserMapper.java | 35 +++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/enums/ErrorCodeConstants.java | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpAutoReplyMatchEnum.java | 28 ++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpAutoReplyTypeEnum.java | 29 +++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpMessageSendFromEnum.java | 28 ++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/config/MpConfiguration.java | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/DefaultMpServiceFactory.java | 177 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/MpServiceFactory.java | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/context/MpContextHolder.java | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/util/MpUtils.java | 167 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/framework/package-info.java | 6 ++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/framework/web/config/MpWebConfiguration.java | 24 ++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/framework/web/package-info.java | 4 ++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/package-info.java | 8 ++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/account/MpAccountService.java | 110 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/account/MpAccountServiceImpl.java | 229 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/menu/MenuHandler.java | 34 ++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/message/MessageAutoReplyHandler.java | 41 +++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/message/MessageReceiveHandler.java | 36 ++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/KfSessionHandler.java | 26 ++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/NullHandler.java | 24 ++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/ScanHandler.java | 25 +++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/StoreCheckNotifyHandler.java | 24 ++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/package-info.java | 4 ++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/user/LocationHandler.java | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/user/SubscribeHandler.java | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/user/UnsubscribeHandler.java | 39 +++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/material/MpMaterialService.java | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/material/MpMaterialServiceImpl.java | 224 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/menu/MpMenuService.java | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/menu/MpMenuServiceImpl.java | 171 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpAutoReplyService.java | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpAutoReplyServiceImpl.java | 202 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageService.java | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageServiceImpl.java | 172 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/message/bo/MpMessageSendOutReqBO.java | 110 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/statistics/MpStatisticsService.java | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/statistics/MpStatisticsServiceImpl.java | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/tag/MpTagService.java | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/tag/MpTagServiceImpl.java | 164 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/user/MpUserService.java | 102 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/user/MpUserServiceImpl.java | 215 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mp/yudao-module-mp-api/pom.xml | 26 -------------------------- yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/ErrorCodeConstants.java | 64 ---------------------------------------------------------------- yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpAutoReplyMatchEnum.java | 28 ---------------------------- yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpAutoReplyTypeEnum.java | 29 ----------------------------- yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpMessageSendFromEnum.java | 28 ---------------------------- yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/package-info.java | 8 -------- yudao-module-mp/yudao-module-mp-biz/pom.xml | 91 ------------------------------------------------------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/MpAccountController.java | 98 -------------------------------------------------------------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountBaseVO.java | 43 ------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountCreateReqVO.java | 14 -------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountPageReqVO.java | 24 ------------------------ yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountRespVO.java | 25 ------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountSimpleRespVO.java | 16 ---------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountUpdateReqVO.java | 20 -------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/MpMaterialController.http | 5 ----- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/MpMaterialController.java | 74 -------------------------------------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialPageReqVO.java | 27 --------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialRespVO.java | 47 ----------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialUploadNewsImageReqVO.java | 23 ----------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialUploadPermanentReqVO.java | 53 ----------------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialUploadRespVO.java | 16 ---------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialUploadTemporaryReqVO.java | 28 ---------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/MpMenuController.http | 50 -------------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/MpMenuController.java | 57 --------------------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/vo/MpMenuBaseVO.java | 115 ------------------------------------------------------------------------------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/vo/MpMenuRespVO.java | 28 ---------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/vo/MpMenuSaveReqVO.java | 34 ---------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpAutoReplyController.http | 5 ----- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpAutoReplyController.java | 74 -------------------------------------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpMessageController.http | 33 --------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpMessageController.java | 47 ----------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyBaseVO.java | 109 ------------------------------------------------------------------------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyCreateReqVO.java | 20 -------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyPageReqVO.java | 21 --------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyRespVO.java | 27 --------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyUpdateReqVO.java | 20 -------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/message/MpMessagePageReqVO.java | 35 ----------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/message/MpMessageRespVO.java | 101 ----------------------------------------------------------------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/message/MpMessageSendReqVO.java | 58 ---------------------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/MpDraftController.http | 54 ------------------------------------------------------ yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/MpDraftController.java | 136 ---------------------------------------------------------------------------------------------------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/MpFreePublishController.http | 13 ------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/MpFreePublishController.java | 119 ----------------------------------------------------------------------------------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/vo/MpDraftPageReqVO.java | 21 --------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/vo/MpFreePublishPageReqVO.java | 21 --------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/open/MpOpenController.java | 117 --------------------------------------------------------------------------------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/open/vo/MpOpenCheckSignatureReqVO.java | 29 ----------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/open/vo/MpOpenHandleMessageReqVO.java | 37 ------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/MpStatisticsController.java | 68 -------------------------------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsGetReqVO.java | 25 ------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsInterfaceSummaryRespVO.java | 27 --------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsUpstreamMessageRespVO.java | 21 --------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsUserCumulateRespVO.java | 18 ------------------ yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsUserSummaryRespVO.java | 24 ------------------------ yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/MpTagController.http | 39 --------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/MpTagController.java | 88 ---------------------------------------------------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagBaseVO.java | 21 --------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagCreateReqVO.java | 20 -------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagPageReqVO.java | 24 ------------------------ yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagRespVO.java | 25 ------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagSimpleRespVO.java | 19 ------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagUpdateReqVO.java | 20 -------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/MpUserController.http | 18 ------------------ yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/MpUserController.java | 65 ----------------------------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/vo/MpUserPageReqVO.java | 30 ------------------------------ yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/vo/MpUserRespVO.java | 55 ------------------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/vo/MpUserUpdateReqVO.java | 26 -------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/package-info.java | 6 ------ yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/account/MpAccountConvert.java | 31 ------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/material/MpMaterialConvert.java | 47 ----------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/menu/MpMenuConvert.java | 50 -------------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/message/MpAutoReplyConvert.java | 37 ------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/message/MpMessageConvert.java | 172 ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/statistics/MpStatisticsConvert.java | 52 ---------------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/tag/MpTagConvert.java | 44 -------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/user/MpUserConvert.java | 56 -------------------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/account/MpAccountDO.java | 62 -------------------------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/material/MpMaterialDO.java | 99 --------------------------------------------------------------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/menu/MpMenuDO.java | 185 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/message/MpAutoReplyDO.java | 165 --------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/message/MpMessageDO.java | 242 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/tag/MpTagDO.java | 58 ---------------------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/user/MpUserDO.java | 114 ------------------------------------------------------------------------------------------------------------------ yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/account/MpAccountMapper.java | 31 ------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/material/MpMaterialMapper.java | 33 --------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/menu/MpMenuMapper.java | 25 ------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/message/MpAutoReplyMapper.java | 70 ---------------------------------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/message/MpMessageMapper.java | 22 ---------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/tag/MpTagMapper.java | 26 -------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/user/MpUserMapper.java | 35 ----------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/config/MpConfiguration.java | 54 ------------------------------------------------------ yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/DefaultMpServiceFactory.java | 177 --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/MpServiceFactory.java | 66 ------------------------------------------------------------------ yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/context/MpContextHolder.java | 53 ----------------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/util/MpUtils.java | 167 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/package-info.java | 6 ------ yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/web/config/MpWebConfiguration.java | 24 ------------------------ yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/web/package-info.java | 4 ---- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/package-info.java | 8 -------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/account/MpAccountService.java | 110 -------------------------------------------------------------------------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/account/MpAccountServiceImpl.java | 229 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/menu/MenuHandler.java | 34 ---------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/message/MessageAutoReplyHandler.java | 41 ----------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/message/MessageReceiveHandler.java | 36 ------------------------------------ yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/KfSessionHandler.java | 26 -------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/NullHandler.java | 24 ------------------------ yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/ScanHandler.java | 25 ------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/StoreCheckNotifyHandler.java | 24 ------------------------ yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/package-info.java | 4 ---- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/user/LocationHandler.java | 49 ------------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/user/SubscribeHandler.java | 52 ---------------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/user/UnsubscribeHandler.java | 39 --------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/material/MpMaterialService.java | 84 ------------------------------------------------------------------------------------ yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/material/MpMaterialServiceImpl.java | 224 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/menu/MpMenuService.java | 49 ------------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/menu/MpMenuServiceImpl.java | 171 --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpAutoReplyService.java | 75 --------------------------------------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpAutoReplyServiceImpl.java | 202 ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageService.java | 59 ----------------------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageServiceImpl.java | 172 ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/bo/MpMessageSendOutReqBO.java | 110 -------------------------------------------------------------------------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/statistics/MpStatisticsService.java | 54 ------------------------------------------------------ yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/statistics/MpStatisticsServiceImpl.java | 77 ----------------------------------------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/tag/MpTagService.java | 65 ----------------------------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/tag/MpTagServiceImpl.java | 164 -------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/user/MpUserService.java | 102 ------------------------------------------------------------------------------------------------------ yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/user/MpUserServiceImpl.java | 215 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-server/pom.xml | 2 +- 253 files changed, 7484 insertions(+), 7546 deletions(-) create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/MpAccountController.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountBaseVO.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountCreateReqVO.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountPageReqVO.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountRespVO.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountSimpleRespVO.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountUpdateReqVO.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/MpMaterialController.http create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/MpMaterialController.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialPageReqVO.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialRespVO.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialUploadNewsImageReqVO.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialUploadPermanentReqVO.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialUploadRespVO.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialUploadTemporaryReqVO.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/MpMenuController.http create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/MpMenuController.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/vo/MpMenuBaseVO.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/vo/MpMenuRespVO.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/vo/MpMenuSaveReqVO.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpAutoReplyController.http create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpAutoReplyController.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpMessageController.http create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpMessageController.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyBaseVO.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyCreateReqVO.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyPageReqVO.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyRespVO.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyUpdateReqVO.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/message/MpMessagePageReqVO.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/message/MpMessageRespVO.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/message/MpMessageSendReqVO.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/MpDraftController.http create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/MpDraftController.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/MpFreePublishController.http create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/MpFreePublishController.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/vo/MpDraftPageReqVO.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/vo/MpFreePublishPageReqVO.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/open/MpOpenController.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/open/vo/MpOpenCheckSignatureReqVO.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/open/vo/MpOpenHandleMessageReqVO.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/MpStatisticsController.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsGetReqVO.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsInterfaceSummaryRespVO.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsUpstreamMessageRespVO.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsUserCumulateRespVO.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsUserSummaryRespVO.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/MpTagController.http create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/MpTagController.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagBaseVO.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagCreateReqVO.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagPageReqVO.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagRespVO.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagSimpleRespVO.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagUpdateReqVO.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/MpUserController.http create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/MpUserController.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/vo/MpUserPageReqVO.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/vo/MpUserRespVO.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/vo/MpUserUpdateReqVO.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/package-info.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/convert/account/MpAccountConvert.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/convert/material/MpMaterialConvert.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/convert/menu/MpMenuConvert.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/convert/message/MpAutoReplyConvert.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/convert/message/MpMessageConvert.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/convert/statistics/MpStatisticsConvert.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/convert/tag/MpTagConvert.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/convert/user/MpUserConvert.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/account/MpAccountDO.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/material/MpMaterialDO.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/menu/MpMenuDO.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/message/MpAutoReplyDO.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/message/MpMessageDO.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/tag/MpTagDO.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/user/MpUserDO.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/account/MpAccountMapper.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/material/MpMaterialMapper.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/menu/MpMenuMapper.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/message/MpAutoReplyMapper.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/message/MpMessageMapper.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/tag/MpTagMapper.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/user/MpUserMapper.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/enums/ErrorCodeConstants.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpAutoReplyMatchEnum.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpAutoReplyTypeEnum.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpMessageSendFromEnum.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/config/MpConfiguration.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/DefaultMpServiceFactory.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/MpServiceFactory.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/context/MpContextHolder.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/util/MpUtils.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/framework/package-info.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/framework/web/config/MpWebConfiguration.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/framework/web/package-info.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/package-info.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/account/MpAccountService.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/account/MpAccountServiceImpl.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/menu/MenuHandler.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/message/MessageAutoReplyHandler.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/message/MessageReceiveHandler.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/KfSessionHandler.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/NullHandler.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/ScanHandler.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/StoreCheckNotifyHandler.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/package-info.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/user/LocationHandler.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/user/SubscribeHandler.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/user/UnsubscribeHandler.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/material/MpMaterialService.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/material/MpMaterialServiceImpl.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/menu/MpMenuService.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/menu/MpMenuServiceImpl.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpAutoReplyService.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpAutoReplyServiceImpl.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageService.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageServiceImpl.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/message/bo/MpMessageSendOutReqBO.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/statistics/MpStatisticsService.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/statistics/MpStatisticsServiceImpl.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/tag/MpTagService.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/tag/MpTagServiceImpl.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/user/MpUserService.java create mode 100644 yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/user/MpUserServiceImpl.java delete mode 100644 yudao-module-mp/yudao-module-mp-api/pom.xml delete mode 100644 yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/ErrorCodeConstants.java delete mode 100644 yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpAutoReplyMatchEnum.java delete mode 100644 yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpAutoReplyTypeEnum.java delete mode 100644 yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpMessageSendFromEnum.java delete mode 100644 yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/package-info.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/pom.xml delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/MpAccountController.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountBaseVO.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountCreateReqVO.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountPageReqVO.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountRespVO.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountSimpleRespVO.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountUpdateReqVO.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/MpMaterialController.http delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/MpMaterialController.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialPageReqVO.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialRespVO.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialUploadNewsImageReqVO.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialUploadPermanentReqVO.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialUploadRespVO.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialUploadTemporaryReqVO.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/MpMenuController.http delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/MpMenuController.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/vo/MpMenuBaseVO.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/vo/MpMenuRespVO.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/vo/MpMenuSaveReqVO.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpAutoReplyController.http delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpAutoReplyController.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpMessageController.http delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpMessageController.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyBaseVO.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyCreateReqVO.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyPageReqVO.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyRespVO.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyUpdateReqVO.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/message/MpMessagePageReqVO.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/message/MpMessageRespVO.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/message/MpMessageSendReqVO.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/MpDraftController.http delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/MpDraftController.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/MpFreePublishController.http delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/MpFreePublishController.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/vo/MpDraftPageReqVO.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/vo/MpFreePublishPageReqVO.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/open/MpOpenController.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/open/vo/MpOpenCheckSignatureReqVO.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/open/vo/MpOpenHandleMessageReqVO.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/MpStatisticsController.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsGetReqVO.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsInterfaceSummaryRespVO.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsUpstreamMessageRespVO.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsUserCumulateRespVO.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsUserSummaryRespVO.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/MpTagController.http delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/MpTagController.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagBaseVO.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagCreateReqVO.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagPageReqVO.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagRespVO.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagSimpleRespVO.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagUpdateReqVO.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/MpUserController.http delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/MpUserController.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/vo/MpUserPageReqVO.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/vo/MpUserRespVO.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/vo/MpUserUpdateReqVO.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/package-info.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/account/MpAccountConvert.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/material/MpMaterialConvert.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/menu/MpMenuConvert.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/message/MpAutoReplyConvert.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/message/MpMessageConvert.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/statistics/MpStatisticsConvert.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/tag/MpTagConvert.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/user/MpUserConvert.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/account/MpAccountDO.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/material/MpMaterialDO.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/menu/MpMenuDO.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/message/MpAutoReplyDO.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/message/MpMessageDO.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/tag/MpTagDO.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/user/MpUserDO.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/account/MpAccountMapper.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/material/MpMaterialMapper.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/menu/MpMenuMapper.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/message/MpAutoReplyMapper.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/message/MpMessageMapper.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/tag/MpTagMapper.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/user/MpUserMapper.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/config/MpConfiguration.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/DefaultMpServiceFactory.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/MpServiceFactory.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/context/MpContextHolder.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/util/MpUtils.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/package-info.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/web/config/MpWebConfiguration.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/web/package-info.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/package-info.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/account/MpAccountService.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/account/MpAccountServiceImpl.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/menu/MenuHandler.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/message/MessageAutoReplyHandler.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/message/MessageReceiveHandler.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/KfSessionHandler.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/NullHandler.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/ScanHandler.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/StoreCheckNotifyHandler.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/package-info.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/user/LocationHandler.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/user/SubscribeHandler.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/user/UnsubscribeHandler.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/material/MpMaterialService.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/material/MpMaterialServiceImpl.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/menu/MpMenuService.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/menu/MpMenuServiceImpl.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpAutoReplyService.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpAutoReplyServiceImpl.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageService.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageServiceImpl.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/bo/MpMessageSendOutReqBO.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/statistics/MpStatisticsService.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/statistics/MpStatisticsServiceImpl.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/tag/MpTagService.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/tag/MpTagServiceImpl.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/user/MpUserService.java delete mode 100644 yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/user/MpUserServiceImpl.java diff --git a/yudao-module-mp/pom.xml b/yudao-module-mp/pom.xml index b339c40..01c9552 100644 --- a/yudao-module-mp/pom.xml +++ b/yudao-module-mp/pom.xml @@ -10,15 +10,78 @@ 4.0.0 yudao-module-mp - pom + jar + ${project.artifactId} - wechat 模块,主要实现微信平台的相关业务。 - 例如:微信公众号、企业微信 SCRM 等 + mp 模块,我们放微信微信公众号。 + 例如说:提供微信公众号的账号、菜单、粉丝、标签、消息、自动回复、素材、模板通知、运营数据等功能 - - yudao-module-mp-api - yudao-module-mp-biz - + + + + cn.iocoder.boot + yudao-module-system + ${revision} + + + cn.iocoder.boot + yudao-module-infra + ${revision} + + + + + cn.iocoder.boot + yudao-spring-boot-starter-biz-tenant + + + + + cn.iocoder.boot + yudao-spring-boot-starter-security + + + + org.springframework.boot + spring-boot-starter-validation + + + + + cn.iocoder.boot + yudao-spring-boot-starter-mybatis + + + + cn.iocoder.boot + yudao-spring-boot-starter-redis + + + + + cn.iocoder.boot + yudao-spring-boot-starter-mq + + + + + cn.iocoder.boot + yudao-spring-boot-starter-test + test + + + + + cn.iocoder.boot + yudao-spring-boot-starter-excel + + + + + com.github.binarywang + wx-java-mp-spring-boot-starter + + diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/MpAccountController.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/MpAccountController.java new file mode 100644 index 0000000..8a77a28 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/MpAccountController.java @@ -0,0 +1,98 @@ +package cn.iocoder.yudao.module.mp.controller.admin.account; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.mp.controller.admin.account.vo.*; +import cn.iocoder.yudao.module.mp.convert.account.MpAccountConvert; +import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; +import cn.iocoder.yudao.module.mp.service.account.MpAccountService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 公众号账号") +@RestController +@RequestMapping("/mp/account") +@Validated +public class MpAccountController { + + @Resource + private MpAccountService mpAccountService; + + @PostMapping("/create") + @Operation(summary = "创建公众号账号") + @PreAuthorize("@ss.hasPermission('mp:account:create')") + public CommonResult createAccount(@Valid @RequestBody MpAccountCreateReqVO createReqVO) { + return success(mpAccountService.createAccount(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新公众号账号") + @PreAuthorize("@ss.hasPermission('mp:account:update')") + public CommonResult updateAccount(@Valid @RequestBody MpAccountUpdateReqVO updateReqVO) { + mpAccountService.updateAccount(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除公众号账号") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('mp:account:delete')") + public CommonResult deleteAccount(@RequestParam("id") Long id) { + mpAccountService.deleteAccount(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得公众号账号") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('mp:account:query')") + public CommonResult getAccount(@RequestParam("id") Long id) { + MpAccountDO wxAccount = mpAccountService.getAccount(id); + return success(MpAccountConvert.INSTANCE.convert(wxAccount)); + } + + @GetMapping("/page") + @Operation(summary = "获得公众号账号分页") + @PreAuthorize("@ss.hasPermission('mp:account:query')") + public CommonResult> getAccountPage(@Valid MpAccountPageReqVO pageVO) { + PageResult pageResult = mpAccountService.getAccountPage(pageVO); + return success(MpAccountConvert.INSTANCE.convertPage(pageResult)); + } + + @GetMapping("/list-all-simple") + @Operation(summary = "获取公众号账号精简信息列表") + @PreAuthorize("@ss.hasPermission('mp:account:query')") + public CommonResult> getSimpleAccounts() { + List list = mpAccountService.getAccountList(); + return success(MpAccountConvert.INSTANCE.convertList02(list)); + } + + @PutMapping("/generate-qr-code") + @Operation(summary = "生成公众号二维码") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('mp:account:qr-code')") + public CommonResult generateAccountQrCode(@RequestParam("id") Long id) { + mpAccountService.generateAccountQrCode(id); + return success(true); + } + + @PutMapping("/clear-quota") + @Operation(summary = "清空公众号 API 配额") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('mp:account:clear-quota')") + public CommonResult clearAccountQuota(@RequestParam("id") Long id) { + mpAccountService.clearAccountQuota(id); + return success(true); + } + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountBaseVO.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountBaseVO.java new file mode 100644 index 0000000..92eecf9 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountBaseVO.java @@ -0,0 +1,43 @@ +package cn.iocoder.yudao.module.mp.controller.admin.account.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import jakarta.validation.constraints.NotEmpty; + +/** + * 公众号账号 Base VO,提供给添加、修改、详细的子 VO 使用 + * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 + * + * @author fengdan + */ +@Data +public class MpAccountBaseVO { + + @Schema(description = "公众号名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道源码") + @NotEmpty(message = "公众号名称不能为空") + private String name; + + @Schema(description = "公众号微信号", requiredMode = Schema.RequiredMode.REQUIRED, example = "yudaoyuanma") + @NotEmpty(message = "公众号微信号不能为空") + private String account; + + @Schema(description = "公众号 appId", requiredMode = Schema.RequiredMode.REQUIRED, example = "wx5b23ba7a5589ecbb") + @NotEmpty(message = "公众号 appId 不能为空") + private String appId; + + @Schema(description = "公众号密钥", requiredMode = Schema.RequiredMode.REQUIRED, example = "3a7b3b20c537e52e74afd395eb85f61f") + @NotEmpty(message = "公众号密钥不能为空") + private String appSecret; + + @Schema(description = "公众号 token", requiredMode = Schema.RequiredMode.REQUIRED, example = "kangdayuzhen") + @NotEmpty(message = "公众号 token 不能为空") + private String token; + + @Schema(description = "加密密钥", example = "gjN+Ksei") + private String aesKey; + + @Schema(description = "备注", example = "请关注芋道源码,学习技术") + private String remark; + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountCreateReqVO.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountCreateReqVO.java new file mode 100644 index 0000000..b864f16 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountCreateReqVO.java @@ -0,0 +1,14 @@ +package cn.iocoder.yudao.module.mp.controller.admin.account.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@Schema(description = "管理后台 - 公众号账号创建 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class MpAccountCreateReqVO extends MpAccountBaseVO { + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountPageReqVO.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountPageReqVO.java new file mode 100644 index 0000000..93093dc --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountPageReqVO.java @@ -0,0 +1,24 @@ +package cn.iocoder.yudao.module.mp.controller.admin.account.vo; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@Schema(description = "管理后台 - 公众号账号分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class MpAccountPageReqVO extends PageParam { + + @Schema(name = "公众号名称", description = "模糊匹配") + private String name; + + @Schema(name = "公众号账号", description = "模糊匹配") + private String account; + + @Schema(name = "公众号 appid", description = "模糊匹配") + private String appId; + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountRespVO.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountRespVO.java new file mode 100644 index 0000000..0f7ca7e --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountRespVO.java @@ -0,0 +1,25 @@ +package cn.iocoder.yudao.module.mp.controller.admin.account.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 公众号账号 Response VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class MpAccountRespVO extends MpAccountBaseVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "二维码图片URL", example = "https://www.iocoder.cn/1024.png") + private String qrCodeUrl; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountSimpleRespVO.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountSimpleRespVO.java new file mode 100644 index 0000000..db4594f --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountSimpleRespVO.java @@ -0,0 +1,16 @@ +package cn.iocoder.yudao.module.mp.controller.admin.account.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 公众号账号精简信息 Response VO") +@Data +public class MpAccountSimpleRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "公众号名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道源码") + private String name; + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountUpdateReqVO.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountUpdateReqVO.java new file mode 100644 index 0000000..a5bec7c --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountUpdateReqVO.java @@ -0,0 +1,20 @@ +package cn.iocoder.yudao.module.mp.controller.admin.account.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import jakarta.validation.constraints.NotNull; + +@Schema(description = "管理后台 - 公众号账号更新 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class MpAccountUpdateReqVO extends MpAccountBaseVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotNull(message = "编号不能为空") + private Long id; + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/MpMaterialController.http b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/MpMaterialController.http new file mode 100644 index 0000000..c093adf --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/MpMaterialController.http @@ -0,0 +1,5 @@ +### 请求 /mp/material/page 接口 => 成功 +GET {{baseUrl}}/mp/material/page?permanent=true&pageNo=1&pageSize=10 +Content-Type: application/json +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/MpMaterialController.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/MpMaterialController.java new file mode 100644 index 0000000..9db08bb --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/MpMaterialController.java @@ -0,0 +1,74 @@ +package cn.iocoder.yudao.module.mp.controller.admin.material; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.mp.controller.admin.material.vo.*; +import cn.iocoder.yudao.module.mp.convert.material.MpMaterialConvert; +import cn.iocoder.yudao.module.mp.dal.dataobject.material.MpMaterialDO; +import cn.iocoder.yudao.module.mp.service.material.MpMaterialService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import java.io.IOException; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 公众号素材") +@RestController +@RequestMapping("/mp/material") +@Validated +public class MpMaterialController { + + @Resource + private MpMaterialService mpMaterialService; + + @Operation(summary = "上传临时素材") + @PostMapping("/upload-temporary") + @PreAuthorize("@ss.hasPermission('mp:material:upload-temporary')") + public CommonResult uploadTemporaryMaterial( + @Valid MpMaterialUploadTemporaryReqVO reqVO) throws IOException { + MpMaterialDO material = mpMaterialService.uploadTemporaryMaterial(reqVO); + return success(MpMaterialConvert.INSTANCE.convert(material)); + } + + @Operation(summary = "上传永久素材") + @PostMapping("/upload-permanent") + @PreAuthorize("@ss.hasPermission('mp:material:upload-permanent')") + public CommonResult uploadPermanentMaterial( + @Valid MpMaterialUploadPermanentReqVO reqVO) throws IOException { + MpMaterialDO material = mpMaterialService.uploadPermanentMaterial(reqVO); + return success(MpMaterialConvert.INSTANCE.convert(material)); + } + + @Operation(summary = "删除素材") + @DeleteMapping("/delete-permanent") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('mp:material:delete')") + public CommonResult deleteMaterial(@RequestParam("id") Long id) { + mpMaterialService.deleteMaterial(id); + return success(true); + } + + @Operation(summary = "上传图文内容中的图片") + @PostMapping("/upload-news-image") + @PreAuthorize("@ss.hasPermission('mp:material:upload-news-image')") + public CommonResult uploadNewsImage(@Valid MpMaterialUploadNewsImageReqVO reqVO) + throws IOException { + return success(mpMaterialService.uploadNewsImage(reqVO)); + } + + @Operation(summary = "获得素材分页") + @GetMapping("/page") + @PreAuthorize("@ss.hasPermission('mp:material:query')") + public CommonResult> getMaterialPage(@Valid MpMaterialPageReqVO pageReqVO) { + PageResult pageResult = mpMaterialService.getMaterialPage(pageReqVO); + return success(MpMaterialConvert.INSTANCE.convertPage(pageResult)); + } + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialPageReqVO.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialPageReqVO.java new file mode 100644 index 0000000..16e6eb5 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialPageReqVO.java @@ -0,0 +1,27 @@ +package cn.iocoder.yudao.module.mp.controller.admin.material.vo; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import jakarta.validation.constraints.NotNull; + +@Schema(description = "管理后台 - 公众号素材的分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class MpMaterialPageReqVO extends PageParam { + + @Schema(description = "公众号账号的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") + @NotNull(message = "公众号账号的编号不能为空") + private Long accountId; + + @Schema(description = "是否永久", example = "true") + private Boolean permanent; + + @Schema(description = "文件类型 参见 WxConsts.MediaFileType 枚举", example = "image") + private String type; + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialRespVO.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialRespVO.java new file mode 100644 index 0000000..5aba24e --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialRespVO.java @@ -0,0 +1,47 @@ +package cn.iocoder.yudao.module.mp.controller.admin.material.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 公众号素材 Response VO") +@Data +public class MpMaterialRespVO { + + @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "公众号账号的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Long accountId; + @Schema(description = "公众号账号的 appId", requiredMode = Schema.RequiredMode.REQUIRED, example = "wx1234567890") + private String appId; + + @Schema(description = "素材的 media_id", requiredMode = Schema.RequiredMode.REQUIRED, example = "123") + private String mediaId; + + @Schema(description = "文件类型 参见 WxConsts.MediaFileType 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "image") + private String type; + + @Schema(description = "是否永久 true - 永久;false - 临时", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") + private Boolean permanent; + + @Schema(description = "素材的 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.png") + private String url; + + + @Schema(description = "名字", example = "yunai.png") + private String name; + + @Schema(description = "公众号文件 URL 只有【永久素材】使用", example = "https://mmbiz.qpic.cn/xxx.mp3") + private String mpUrl; + + @Schema(description = "视频素材的标题 只有【永久素材】使用", example = "我是标题") + private String title; + @Schema(description = "视频素材的描述 只有【永久素材】使用", example = "我是介绍") + private String introduction; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialUploadNewsImageReqVO.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialUploadNewsImageReqVO.java new file mode 100644 index 0000000..070957f --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialUploadNewsImageReqVO.java @@ -0,0 +1,23 @@ +package cn.iocoder.yudao.module.mp.controller.admin.material.vo; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springframework.web.multipart.MultipartFile; + +import jakarta.validation.constraints.NotNull; + +@Schema(description = "管理后台 - 公众号素材上传图文内容中的图片 Request VO") +@Data +public class MpMaterialUploadNewsImageReqVO { + + @Schema(description = "公众号账号的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") + @NotNull(message = "公众号账号的编号不能为空") + private Long accountId; + + @Schema(description = "文件附件", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "文件不能为空") + @JsonIgnore // 避免被操作日志,进行序列化,导致报错 + private MultipartFile file; + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialUploadPermanentReqVO.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialUploadPermanentReqVO.java new file mode 100644 index 0000000..8f4ff88 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialUploadPermanentReqVO.java @@ -0,0 +1,53 @@ +package cn.iocoder.yudao.module.mp.controller.admin.material.vo; + +import cn.hutool.core.util.ObjectUtil; +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import me.chanjar.weixin.common.api.WxConsts; +import org.springframework.web.multipart.MultipartFile; + +import jakarta.validation.constraints.AssertTrue; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; + +@Schema(description = "管理后台 - 公众号素材上传永久 Request VO") +@Data +public class MpMaterialUploadPermanentReqVO { + + @Schema(description = "公众号账号的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") + @NotNull(message = "公众号账号的编号不能为空") + private Long accountId; + + @Schema(description = "文件类型 参见 WxConsts.MediaFileType 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "image") + @NotEmpty(message = "文件类型不能为空") + private String type; + + @Schema(description = "文件附件", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "文件不能为空") + @JsonIgnore // 避免被操作日志,进行序列化,导致报错 + private MultipartFile file; + + @Schema(description = "名字 如果 name 为空,则使用 file 文件名", example = "wechat.mp") + private String name; + + @Schema(description = "视频素材的标题 文件类型为 video 时,必填", example = "视频素材的标题") + private String title; + @Schema(description = "视频素材的描述 文件类型为 video 时,必填", example = "视频素材的描述") + private String introduction; + + @AssertTrue(message = "标题不能为空") + public boolean isTitleValid() { + // 生成场景为管理后台时,必须设置上级菜单,不然生成的菜单 SQL 是无父级菜单的 + return ObjectUtil.notEqual(type, WxConsts.MediaFileType.VIDEO) + || title != null; + } + + @AssertTrue(message = "描述不能为空") + public boolean isIntroductionValid() { + // 生成场景为管理后台时,必须设置上级菜单,不然生成的菜单 SQL 是无父级菜单的 + return ObjectUtil.notEqual(type, WxConsts.MediaFileType.VIDEO) + || introduction != null; + } + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialUploadRespVO.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialUploadRespVO.java new file mode 100644 index 0000000..0174cbc --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialUploadRespVO.java @@ -0,0 +1,16 @@ +package cn.iocoder.yudao.module.mp.controller.admin.material.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 公众号素材上传结果 Response VO") +@Data +public class MpMaterialUploadRespVO { + + @Schema(description = "素材的 media_id", requiredMode = Schema.RequiredMode.REQUIRED, example = "123") + private String mediaId; + + @Schema(description = "素材的 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.png") + private String url; + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialUploadTemporaryReqVO.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialUploadTemporaryReqVO.java new file mode 100644 index 0000000..139828e --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialUploadTemporaryReqVO.java @@ -0,0 +1,28 @@ +package cn.iocoder.yudao.module.mp.controller.admin.material.vo; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springframework.web.multipart.MultipartFile; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; + +@Schema(description = "管理后台 - 公众号素材上传临时 Request VO") +@Data +public class MpMaterialUploadTemporaryReqVO { + + @Schema(description = "公众号账号的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") + @NotNull(message = "公众号账号的编号不能为空") + private Long accountId; + + @Schema(description = "文件类型 参见 WxConsts.MediaFileType 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "image") + @NotEmpty(message = "文件类型不能为空") + private String type; + + @Schema(description = "文件附件", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "文件不能为空") + @JsonIgnore // 避免被操作日志,进行序列化,导致报错 + private MultipartFile file; + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/MpMenuController.http b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/MpMenuController.http new file mode 100644 index 0000000..defd7ec --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/MpMenuController.http @@ -0,0 +1,50 @@ +### 请求 /mp/menu/save 接口 => 成功 +POST {{baseUrl}}/mp/menu/save +Content-Type: application/json +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} + +{ + "accountId": "1", + "menus": [ + { + "type":"click", + "name":"今日歌曲", + "menuKey":"V1001_TODAY_MUSIC" + }, + { + "name":"搜索", + "type":"view", + "url":"https://www.soso.com/" + }, + { + "name": "父按钮", + "children": [ + { + "type":"click", + "name":"归去来兮", + "menuKey":"MUSIC" + }, + { + "name":"不说", + "type":"view", + "url":"https://www.soso.com/" + }] + }] +} + +### 请求 /mp/menu/save 接口 => 成功(清空) +POST {{baseUrl}}/mp/menu/save +Content-Type: application/json +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} + +{ + "accountId": "1", + "menus": [] +} + +### 请求 /mp/menu/list 接口 => 成功 +GET {{baseUrl}}/mp/menu/list?accountId=1 +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/MpMenuController.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/MpMenuController.java new file mode 100644 index 0000000..28e321a --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/MpMenuController.java @@ -0,0 +1,57 @@ +package cn.iocoder.yudao.module.mp.controller.admin.menu; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.module.mp.controller.admin.menu.vo.MpMenuRespVO; +import cn.iocoder.yudao.module.mp.controller.admin.menu.vo.MpMenuSaveReqVO; +import cn.iocoder.yudao.module.mp.convert.menu.MpMenuConvert; +import cn.iocoder.yudao.module.mp.dal.dataobject.menu.MpMenuDO; +import cn.iocoder.yudao.module.mp.service.menu.MpMenuService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 公众号菜单") +@RestController +@RequestMapping("/mp/menu") +@Validated +public class MpMenuController { + + @Resource + private MpMenuService mpMenuService; + + @PostMapping("/save") + @Operation(summary = "保存公众号菜单") + @PreAuthorize("@ss.hasPermission('mp:menu:save')") + public CommonResult saveMenu(@Valid @RequestBody MpMenuSaveReqVO createReqVO) { + mpMenuService.saveMenu(createReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除公众号菜单") + @Parameter(name = "accountId", description = "公众号账号的编号", required = true, example = "10") + @PreAuthorize("@ss.hasPermission('mp:menu:delete')") + public CommonResult deleteMenu(@RequestParam("accountId") Long accountId) { + mpMenuService.deleteMenuByAccountId(accountId); + return success(true); + } + + @GetMapping("/list") + @Operation(summary = "获得公众号菜单列表") + @Parameter(name = "accountId", description = "公众号账号的编号", required = true, example = "10") + @PreAuthorize("@ss.hasPermission('mp:menu:query')") + public CommonResult> getMenuList(@RequestParam("accountId") Long accountId) { + List list = mpMenuService.getMenuListByAccountId(accountId); + return success(MpMenuConvert.INSTANCE.convertList(list)); + } + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/vo/MpMenuBaseVO.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/vo/MpMenuBaseVO.java new file mode 100644 index 0000000..8a6974c --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/vo/MpMenuBaseVO.java @@ -0,0 +1,115 @@ +package cn.iocoder.yudao.module.mp.controller.admin.menu.vo; + +import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import me.chanjar.weixin.common.api.WxConsts; +import org.hibernate.validator.constraints.URL; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import java.util.List; + +import static cn.iocoder.yudao.module.mp.framework.mp.core.util.MpUtils.*; + +/** + * 公众号菜单 Base VO,提供给添加、修改、详细的子 VO 使用 + * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 + */ +@Data +public class MpMenuBaseVO { + + /** + * 菜单名称 + */ + private String name; + /** + * 菜单标识 + * + * 支持多 DB 类型时,无法直接使用 key + @TableField("menuKey") 来实现转换,原因是 "menuKey" AS key 而存在报错 + */ + private String menuKey; + /** + * 父菜单编号 + */ + private Long parentId; + + // ========== 按钮操作 ========== + + /** + * 按钮类型 + * + * 枚举 {@link WxConsts.MenuButtonType} + */ + private String type; + + @Schema(description = "网页链接", example = "https://www.iocoder.cn/") + @NotEmpty(message = "网页链接不能为空", groups = {ViewButtonGroup.class, MiniProgramButtonGroup.class}) + @URL(message = "网页链接必须是 URL 格式") + private String url; + + @Schema(description = "小程序的 appId", example = "wx1234567890") + @NotEmpty(message = "小程序的 appId 不能为空", groups = MiniProgramButtonGroup.class) + private String miniProgramAppId; + + @Schema(description = "小程序的页面路径", example = "pages/index/index") + @NotEmpty(message = "小程序的页面路径不能为空", groups = MiniProgramButtonGroup.class) + private String miniProgramPagePath; + + @Schema(description ="跳转图文的媒体编号", example = "jCQk93AIIgp8ixClWcW_NXXqBKInNWNmq2XnPeDZl7IMVqWiNeL4FfELtggRXd83") + @NotEmpty(message = "跳转图文的媒体编号不能为空", groups = ViewLimitedButtonGroup.class) + private String articleId; + + // ========== 消息内容 ========== + + @Schema(description = "回复的消息类型 枚举 TEXT、IMAGE、VOICE、VIDEO、NEWS、MUSIC", example = "text") + @NotEmpty(message = "回复的消息类型不能为空", groups = {ClickButtonGroup.class, ScanCodeWaitMsgButtonGroup.class}) + private String replyMessageType; + + @Schema(description = "回复的消息内容", example = "欢迎关注") + @NotEmpty(message = "回复的消息内容不能为空", groups = TextMessageGroup.class) + private String replyContent; + + @Schema(description = "回复的媒体 id", example = "123456") + @NotEmpty(message = "回复的消息 mediaId 不能为空", + groups = {ImageMessageGroup.class, VoiceMessageGroup.class, VideoMessageGroup.class}) + private String replyMediaId; + @Schema(description = "回复的媒体 URL", example = "https://www.iocoder.cn/xxx.jpg") + @NotEmpty(message = "回复的消息 mediaId 不能为空", + groups = {ImageMessageGroup.class, VoiceMessageGroup.class, VideoMessageGroup.class}) + private String replyMediaUrl; + + @Schema(description = "缩略图的媒体 id", example = "123456") + @NotEmpty(message = "回复的消息 thumbMediaId 不能为空", groups = {MusicMessageGroup.class}) + private String replyThumbMediaId; + @Schema(description = "缩略图的媒体 URL",example = "https://www.iocoder.cn/xxx.jpg") + @NotEmpty(message = "回复的消息 thumbMedia 地址不能为空", groups = {MusicMessageGroup.class}) + private String replyThumbMediaUrl; + + @Schema(description = "回复的标题", example = "视频标题") + @NotEmpty(message = "回复的消息标题不能为空", groups = VideoMessageGroup.class) + private String replyTitle; + @Schema(description = "回复的描述", example = "视频描述") + @NotEmpty(message = "消息描述不能为空", groups = VideoMessageGroup.class) + private String replyDescription; + + /** + * 回复的图文消息数组 + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS + */ + @NotNull(message = "回复的图文消息不能为空", groups = {NewsMessageGroup.class, ViewLimitedButtonGroup.class}) + @Valid + private List replyArticles; + + @Schema(description = "回复的音乐链接", example = "https://www.iocoder.cn/xxx.mp3") + @NotEmpty(message = "回复的音乐链接不能为空", groups = MusicMessageGroup.class) + @URL(message = "回复的高质量音乐链接格式不正确", groups = MusicMessageGroup.class) + private String replyMusicUrl; + @Schema(description = "高质量音乐链接", example = "https://www.iocoder.cn/xxx.mp3") + @NotEmpty(message = "回复的高质量音乐链接不能为空", groups = MusicMessageGroup.class) + @URL(message = "回复的高质量音乐链接格式不正确", groups = MusicMessageGroup.class) + private String replyHqMusicUrl; + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/vo/MpMenuRespVO.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/vo/MpMenuRespVO.java new file mode 100644 index 0000000..5d54959 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/vo/MpMenuRespVO.java @@ -0,0 +1,28 @@ +package cn.iocoder.yudao.module.mp.controller.admin.menu.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 公众号菜单 Response VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class MpMenuRespVO extends MpMenuBaseVO { + + @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "公众号账号的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") + private Long accountId; + + @Schema(description = "公众号 appId", requiredMode = Schema.RequiredMode.REQUIRED, example = "wx1234567890ox") + private String appId; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/vo/MpMenuSaveReqVO.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/vo/MpMenuSaveReqVO.java new file mode 100644 index 0000000..640c2c1 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/vo/MpMenuSaveReqVO.java @@ -0,0 +1,34 @@ +package cn.iocoder.yudao.module.mp.controller.admin.menu.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import java.util.List; + +@Schema(description = "管理后台 - 公众号菜单保存 Request VO") +@Data +public class MpMenuSaveReqVO { + + @Schema(description = "公众号账号的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") + @NotNull(message = "公众号账号的编号不能为空") + private Long accountId; + + @NotEmpty(message = "菜单不能为空") + @Valid + private List menus; + + @Schema(description = "管理后台 - 公众号菜单保存时的每个菜单") + @Data + public static class Menu extends MpMenuBaseVO { + + /** + * 子菜单数组 + */ + private List children; + + } + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpAutoReplyController.http b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpAutoReplyController.http new file mode 100644 index 0000000..8d17c6b --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpAutoReplyController.http @@ -0,0 +1,5 @@ +### 请求 /mp/message/page 接口 => 成功 +GET {{baseUrl}}/mp/auto-reply/page?accountId=1&pageNo=1&pageSize=10 +Content-Type: application/json +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpAutoReplyController.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpAutoReplyController.java new file mode 100644 index 0000000..0bb7e17 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpAutoReplyController.java @@ -0,0 +1,74 @@ +package cn.iocoder.yudao.module.mp.controller.admin.message; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.mp.controller.admin.message.vo.autoreply.MpAutoReplyCreateReqVO; +import cn.iocoder.yudao.module.mp.controller.admin.message.vo.autoreply.MpAutoReplyRespVO; +import cn.iocoder.yudao.module.mp.controller.admin.message.vo.autoreply.MpAutoReplyUpdateReqVO; +import cn.iocoder.yudao.module.mp.controller.admin.message.vo.message.MpMessagePageReqVO; +import cn.iocoder.yudao.module.mp.convert.message.MpAutoReplyConvert; +import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpAutoReplyDO; +import cn.iocoder.yudao.module.mp.service.message.MpAutoReplyService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import jakarta.annotation.Resource; +import jakarta.validation.Valid; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 公众号自动回复") +@RestController +@RequestMapping("/mp/auto-reply") +@Validated +public class MpAutoReplyController { + + @Resource + private MpAutoReplyService mpAutoReplyService; + + @GetMapping("/page") + @Operation(summary = "获得公众号自动回复分页") + @PreAuthorize("@ss.hasPermission('mp:auto-reply:query')") + public CommonResult> getAutoReplyPage(@Valid MpMessagePageReqVO pageVO) { + PageResult pageResult = mpAutoReplyService.getAutoReplyPage(pageVO); + return success(MpAutoReplyConvert.INSTANCE.convertPage(pageResult)); + } + + @GetMapping("/get") + @Operation(summary = "获得公众号自动回复") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('mp:auto-reply:query')") + public CommonResult getAutoReply(@RequestParam("id") Long id) { + MpAutoReplyDO autoReply = mpAutoReplyService.getAutoReply(id); + return success(MpAutoReplyConvert.INSTANCE.convert(autoReply)); + } + + @PostMapping("/create") + @Operation(summary = "创建公众号自动回复") + @PreAuthorize("@ss.hasPermission('mp:auto-reply:create')") + public CommonResult createAutoReply(@Valid @RequestBody MpAutoReplyCreateReqVO createReqVO) { + return success(mpAutoReplyService.createAutoReply(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新公众号自动回复") + @PreAuthorize("@ss.hasPermission('mp:auto-reply:update')") + public CommonResult updateAutoReply(@Valid @RequestBody MpAutoReplyUpdateReqVO updateReqVO) { + mpAutoReplyService.updateAutoReply(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除公众号自动回复") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('mp:auto-reply:delete')") + public CommonResult deleteAutoReply(@RequestParam("id") Long id) { + mpAutoReplyService.deleteAutoReply(id); + return success(true); + } + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpMessageController.http b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpMessageController.http new file mode 100644 index 0000000..16677d4 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpMessageController.http @@ -0,0 +1,33 @@ +### 请求 /mp/message/page 接口 => 成功 +GET {{baseUrl}}/mp/message/page?accountId=1&pageNo=1&pageSize=10 +Content-Type: application/json +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} + +### 请求 /mp/message/send 接口 => 成功(文本) +POST {{baseUrl}}/mp/message/send +Content-Type: application/json +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} + +{ + "userId": 3, + "type": "text", + "content": "测试消息" +} + +### 请求 /mp/message/send 接口 => 成功(音乐) +POST {{baseUrl}}/mp/message/send +Content-Type: application/json +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} + +{ + "userId": 3, + "type": "music", + "title": "测试音乐标题", + "description": "测试音乐内容", + "musicUrl": "https://www.iocoder.cn/xx.mp3", + "hqMusicUrl": "https://www.iocoder.cn/xx_high.mp3", + "thumbMediaId": "s98Iveeg9vDVFwa9q0u8-zSfdKe3xIzAm7wCrFE4WKGPIo4d9qAhtC-n6qvnyWyH" +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpMessageController.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpMessageController.java new file mode 100644 index 0000000..8c0bd32 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpMessageController.java @@ -0,0 +1,47 @@ +package cn.iocoder.yudao.module.mp.controller.admin.message; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.mp.controller.admin.message.vo.message.MpMessagePageReqVO; +import cn.iocoder.yudao.module.mp.controller.admin.message.vo.message.MpMessageRespVO; +import cn.iocoder.yudao.module.mp.controller.admin.message.vo.message.MpMessageSendReqVO; +import cn.iocoder.yudao.module.mp.convert.message.MpMessageConvert; +import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO; +import cn.iocoder.yudao.module.mp.service.message.MpMessageService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import jakarta.annotation.Resource; +import jakarta.validation.Valid; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 公众号消息") +@RestController +@RequestMapping("/mp/message") +@Validated +public class MpMessageController { + + @Resource + private MpMessageService mpMessageService; + + @GetMapping("/page") + @Operation(summary = "获得公众号消息分页") + @PreAuthorize("@ss.hasPermission('mp:message:query')") + public CommonResult> getMessagePage(@Valid MpMessagePageReqVO pageVO) { + PageResult pageResult = mpMessageService.getMessagePage(pageVO); + return success(MpMessageConvert.INSTANCE.convertPage(pageResult)); + } + + @PostMapping("/send") + @Operation(summary = "给粉丝发送消息") + @PreAuthorize("@ss.hasPermission('mp:message:send')") + public CommonResult sendMessage(@Valid @RequestBody MpMessageSendReqVO reqVO) { + MpMessageDO message = mpMessageService.sendKefuMessage(reqVO); + return success(MpMessageConvert.INSTANCE.convert(message)); + } + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyBaseVO.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyBaseVO.java new file mode 100644 index 0000000..fceb5f7 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyBaseVO.java @@ -0,0 +1,109 @@ +package cn.iocoder.yudao.module.mp.controller.admin.message.vo.autoreply; + +import cn.hutool.core.util.ObjectUtil; +import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO; +import cn.iocoder.yudao.module.mp.enums.message.MpAutoReplyTypeEnum; +import cn.iocoder.yudao.module.mp.framework.mp.core.util.MpUtils.*; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import me.chanjar.weixin.common.api.WxConsts; +import org.hibernate.validator.constraints.URL; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.AssertTrue; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import java.util.List; + +/** + * 公众号自动回复 Base VO,提供给添加、修改、详细的子 VO 使用 + * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 + */ +@Data +public class MpAutoReplyBaseVO { + + @Schema(description = "回复类型 参见 MpAutoReplyTypeEnum 枚举", example = "1") + @NotNull(message = "回复类型不能为空") + private Integer type; + + // ==================== 请求消息 ==================== + + @Schema(description = "请求的关键字 当 type 为 MpAutoReplyTypeEnum#KEYWORD 时,必填", example = "关键字") + private String requestKeyword; + @Schema(description = "请求的匹配方式 当 type 为 MpAutoReplyTypeEnum#KEYWORD 时,必填", example = "1") + private Integer requestMatch; + + @Schema(description = "请求的消息类型 当 type 为 MpAutoReplyTypeEnum#MESSAGE 时,必填", example = "text") + private String requestMessageType; + + // ==================== 响应消息 ==================== + + @Schema(description = "回复的消息类型 枚举 TEXT、IMAGE、VOICE、VIDEO、NEWS、MUSIC", example = "text") + @NotEmpty(message = "回复的消息类型不能为空") + private String responseMessageType; + + @Schema(description = "回复的消息内容", example = "欢迎关注") + @NotEmpty(message = "回复的消息内容不能为空", groups = TextMessageGroup.class) + private String responseContent; + + @Schema(description = "回复的媒体 id", example = "123456") + @NotEmpty(message = "回复的消息 mediaId 不能为空", + groups = {ImageMessageGroup.class, VoiceMessageGroup.class, VideoMessageGroup.class}) + private String responseMediaId; + @Schema(description = "回复的媒体 URL", example = "https://www.iocoder.cn/xxx.jpg") + @NotEmpty(message = "回复的消息 mediaId 不能为空", + groups = {ImageMessageGroup.class, VoiceMessageGroup.class, VideoMessageGroup.class}) + private String responseMediaUrl; + + @Schema(description = "缩略图的媒体 id", example = "123456") + @NotEmpty(message = "回复的消息 thumbMediaId 不能为空", groups = {MusicMessageGroup.class}) + private String responseThumbMediaId; + @Schema(description = "缩略图的媒体 URL",example = "https://www.iocoder.cn/xxx.jpg") + @NotEmpty(message = "回复的消息 thumbMedia 地址不能为空", groups = {MusicMessageGroup.class}) + private String responseThumbMediaUrl; + + @Schema(description = "回复的标题", example = "视频标题") + @NotEmpty(message = "回复的消息标题不能为空", groups = VideoMessageGroup.class) + private String responseTitle; + @Schema(description = "回复的描述", example = "视频描述") + @NotEmpty(message = "消息描述不能为空", groups = VideoMessageGroup.class) + private String responseDescription; + + /** + * 回复的图文消息 + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS + */ + @NotNull(message = "回复的图文消息不能为空", groups = {NewsMessageGroup.class, ViewLimitedButtonGroup.class}) + @Valid + private List responseArticles; + + @Schema(description = "回复的音乐链接", example = "https://www.iocoder.cn/xxx.mp3") + @NotEmpty(message = "回复的音乐链接不能为空", groups = MusicMessageGroup.class) + @URL(message = "回复的高质量音乐链接格式不正确", groups = MusicMessageGroup.class) + private String responseMusicUrl; + @Schema(description = "高质量音乐链接", example = "https://www.iocoder.cn/xxx.mp3") + @NotEmpty(message = "回复的高质量音乐链接不能为空", groups = MusicMessageGroup.class) + @URL(message = "回复的高质量音乐链接格式不正确", groups = MusicMessageGroup.class) + private String responseHqMusicUrl; + + @AssertTrue(message = "请求的关键字不能为空") + public boolean isRequestKeywordValid() { + return ObjectUtil.notEqual(type, MpAutoReplyTypeEnum.KEYWORD) + || requestKeyword != null; + } + + @AssertTrue(message = "请求的关键字的匹配不能为空") + public boolean isRequestMatchValid() { + return ObjectUtil.notEqual(type, MpAutoReplyTypeEnum.KEYWORD) + || requestMatch != null; + } + + @AssertTrue(message = "请求的消息类型不能为空") + public boolean isRequestMessageTypeValid() { + return ObjectUtil.notEqual(type, MpAutoReplyTypeEnum.MESSAGE) + || requestMessageType != null; + } + + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyCreateReqVO.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyCreateReqVO.java new file mode 100644 index 0000000..502c17d --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyCreateReqVO.java @@ -0,0 +1,20 @@ +package cn.iocoder.yudao.module.mp.controller.admin.message.vo.autoreply; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import jakarta.validation.constraints.NotNull; + +@Schema(description = "管理后台 - 公众号自动回复的创建 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class MpAutoReplyCreateReqVO extends MpAutoReplyBaseVO { + + @Schema(description = "公众号账号的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotNull(message = "公众号账号的编号不能为空") + private Long accountId; + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyPageReqVO.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyPageReqVO.java new file mode 100644 index 0000000..70169f4 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyPageReqVO.java @@ -0,0 +1,21 @@ +package cn.iocoder.yudao.module.mp.controller.admin.message.vo.autoreply; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import jakarta.validation.constraints.NotNull; + +@Schema(description = "管理后台 - 公众号自动回复的分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class MpAutoReplyPageReqVO extends PageParam { + + @Schema(description = "公众号账号的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "公众号账号的编号不能为空") + private Long accountId; + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyRespVO.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyRespVO.java new file mode 100644 index 0000000..d1f72f3 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyRespVO.java @@ -0,0 +1,27 @@ +package cn.iocoder.yudao.module.mp.controller.admin.message.vo.autoreply; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 公众号自动回复 Response VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class MpAutoReplyRespVO extends MpAutoReplyBaseVO { + + @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "公众号账号的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long accountId; + @Schema(description = "公众号 appId", requiredMode = Schema.RequiredMode.REQUIRED, example = "wx1234567890") + private String appId; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyUpdateReqVO.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyUpdateReqVO.java new file mode 100644 index 0000000..776400c --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyUpdateReqVO.java @@ -0,0 +1,20 @@ +package cn.iocoder.yudao.module.mp.controller.admin.message.vo.autoreply; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import jakarta.validation.constraints.NotNull; + +@Schema(description = "管理后台 - 公众号自动回复的更新 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class MpAutoReplyUpdateReqVO extends MpAutoReplyBaseVO { + + @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotNull(message = "主键不能为空") + private Long id; + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/message/MpMessagePageReqVO.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/message/MpMessagePageReqVO.java new file mode 100644 index 0000000..d9f7cc8 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/message/MpMessagePageReqVO.java @@ -0,0 +1,35 @@ +package cn.iocoder.yudao.module.mp.controller.admin.message.vo.message; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import jakarta.validation.constraints.NotNull; +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - 公众号消息分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class MpMessagePageReqVO extends PageParam { + + @Schema(description = "公众号账号的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotNull(message = "公众号账号的编号不能为空") + private Long accountId; + + @Schema(description = "消息类型 参见 WxConsts.XmlMsgType 枚举", example = "text") + private String type; + + @Schema(description = "公众号粉丝标识", example = "o6_bmjrPTlm6_2sgVt7hMZOPfL2M") + private String openid; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @Schema(description = "创建时间") + private LocalDateTime[] createTime; + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/message/MpMessageRespVO.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/message/MpMessageRespVO.java new file mode 100644 index 0000000..fda5d26 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/message/MpMessageRespVO.java @@ -0,0 +1,101 @@ +package cn.iocoder.yudao.module.mp.controller.admin.message.vo.message; + +import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import me.chanjar.weixin.common.api.WxConsts; + +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "管理后台 - 公众号消息 Response VO") +@Data +public class MpMessageRespVO { + + @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Integer id; + + @Schema(description = "微信公众号消息 id", requiredMode = Schema.RequiredMode.REQUIRED, example = "23953173569869169") + private Long msgId; + + @Schema(description = "公众号账号的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Long accountId; + @Schema(description = "公众号账号的 appid", requiredMode = Schema.RequiredMode.REQUIRED, example = "wx1234567890") + private String appId; + + @Schema(description = "公众号粉丝编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") + private Long userId; + @Schema(description = "公众号粉丝标志", requiredMode = Schema.RequiredMode.REQUIRED, example = "o6_bmjrPTlm6_2sgVt7hMZOPfL2M") + private String openid; + + @Schema(description = "消息类型 参见 WxConsts.XmlMsgType 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "text") + private String type; + @Schema(description = "消息来源 参见 MpMessageSendFromEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer sendFrom; + + // ========= 普通消息内容 https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_standard_messages.html + + @Schema(description = "消息内容 消息类型为 text 时,才有值", example = "你好呀") + private String content; + + @Schema(description = "媒体素材的编号 消息类型为 image、voice、video 时,才有值", example = "1234567890") + private String mediaId; + @Schema(description = "媒体文件的 URL 消息类型为 image、voice、video 时,才有值", example = "https://www.iocoder.cn/xxx.png") + private String mediaUrl; + + @Schema(description = "语音识别后文本 消息类型为 voice 时,才有值", example = "语音识别后文本") + private String recognition; + @Schema(description = "语音格式 消息类型为 voice 时,才有值", example = "amr") + private String format; + + @Schema(description = "标题 消息类型为 video、music、link 时,才有值", example = "我是标题") + private String title; + + @Schema(description = "描述 消息类型为 video、music 时,才有值", example = "我是描述") + private String description; + + @Schema(description = "缩略图的媒体 id 消息类型为 video、music 时,才有值", example = "1234567890") + private String thumbMediaId; + @Schema(description = "缩略图的媒体 URL 消息类型为 video、music 时,才有值", example = "https://www.iocoder.cn/xxx.png") + private String thumbMediaUrl; + + @Schema(description = "点击图文消息跳转链接 消息类型为 link 时,才有值", example = "https://www.iocoder.cn") + private String url; + + @Schema(description = "地理位置维度 消息类型为 location 时,才有值", example = "23.137466") + private Double locationX; + + @Schema(description = "地理位置经度 消息类型为 location 时,才有值", example = "113.352425") + private Double locationY; + + @Schema(description = "地图缩放大小 消息类型为 location 时,才有值", example = "13") + private Double scale; + + @Schema(description = "详细地址 消息类型为 location 时,才有值", example = "杨浦区黄兴路 221-4 号临") + private String label; + + /** + * 图文消息数组 + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS + */ + private List articles; + + @Schema(description = "音乐链接 消息类型为 music 时,才有值", example = "https://www.iocoder.cn/xxx.mp3") + private String musicUrl; + @Schema(description = "高质量音乐链接 消息类型为 music 时,才有值", example = "https://www.iocoder.cn/xxx.mp3") + private String hqMusicUrl; + + // ========= 事件推送 https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_event_pushes.html + + @Schema(description = "事件类型 参见 WxConsts.EventType 枚举", example = "subscribe") + private String event; + @Schema(description = "事件 Key 参见 WxConsts.EventType 枚举", example = "qrscene_123456") + private String eventKey; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/message/MpMessageSendReqVO.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/message/MpMessageSendReqVO.java new file mode 100644 index 0000000..0c96392 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/message/MpMessageSendReqVO.java @@ -0,0 +1,58 @@ +package cn.iocoder.yudao.module.mp.controller.admin.message.vo.message; + +import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO; +import cn.iocoder.yudao.module.mp.framework.mp.core.util.MpUtils.*; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import java.util.List; + +@Schema(description = "管理后台 - 公众号消息发送 Request VO") +@Data +public class MpMessageSendReqVO { + + @Schema(description = "公众号粉丝的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotNull(message = "公众号粉丝的编号不能为空") + private Long userId; + + // ========== 消息内容 ========== + + @Schema(description = "消息类型 TEXT/IMAGE/VOICE/VIDEO/NEWS", requiredMode = Schema.RequiredMode.REQUIRED, example = "text") + @NotEmpty(message = "消息类型不能为空") + public String type; + + @Schema(description = "消息内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "你好呀") + @NotEmpty(message = "消息内容不能为空", groups = TextMessageGroup.class) + private String content; + + @Schema(description = "媒体 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "qqc_2Fot30Jse-HDoZmo5RrUDijz2nGUkP") + @NotEmpty(message = "消息内容不能为空", groups = {ImageMessageGroup.class, VoiceMessageGroup.class, VideoMessageGroup.class}) + private String mediaId; + + @Schema(description = "标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "没有标题") + @NotEmpty(message = "消息内容不能为空", groups = VideoMessageGroup.class) + private String title; + + @Schema(description = "描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "你猜") + @NotEmpty(message = "消息描述不能为空", groups = VideoMessageGroup.class) + private String description; + + @Schema(description = "缩略图的媒体 id", requiredMode = Schema.RequiredMode.REQUIRED, example = "qqc_2Fot30Jse-HDoZmo5RrUDijz2nGUkP") + @NotEmpty(message = "缩略图的媒体 id 不能为空", groups = MusicMessageGroup.class) + private String thumbMediaId; + + @Schema(description = "图文消息", requiredMode = Schema.RequiredMode.REQUIRED) + @Valid + @NotNull(message = "图文消息不能为空", groups = NewsMessageGroup.class) + private List articles; + + @Schema(description = "音乐链接 消息类型为 MUSIC 时", example = "https://www.iocoder.cn/music.mp3") + private String musicUrl; + + @Schema(description = "高质量音乐链接 消息类型为 MUSIC 时", example = "https://www.iocoder.cn/music.mp3") + private String hqMusicUrl; + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/MpDraftController.http b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/MpDraftController.http new file mode 100644 index 0000000..ea75429 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/MpDraftController.http @@ -0,0 +1,54 @@ +### 请求 /mp/draft/page 接口 => 成功 +GET {{baseUrl}}/mp/draft/page?accountId=1&pageNo=1&pageSize=10 +Content-Type: application/json +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} + +### 请求 /mp/draft/create 接口 => 成功 +POST {{baseUrl}}/mp/draft/create?accountId=1 +Content-Type: application/json +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} + +{ + "articles": [ + { + "title": "我是标题", + "author": "我是作者", + "digest": "我是摘要", + "content": "我是内容", + "contentSourceUrl": "https://www.iocoder.cn", + "thumbMediaId": "r6ryvl6LrxBU0miaST4Y-pIcmK-zAAId-9TGgy-DrSLhjVuWbuT3ZBjk9K1yQ0Dn" + }, + { + "title": "我是标题 2", + "author": "我是作者 2", + "digest": "我是摘要 2", + "content": "我是内容 2", + "contentSourceUrl": "https://www.iocoder.cn", + "thumbMediaId": "r6ryvl6LrxBU0miaST4Y-pIcmK-zAAId-9TGgy-DrSLhjVuWbuT3ZBjk9K1yQ0Dn" + } + ] +} + +### 请求 /mp/draft/create 接口 => 成功 +PUT {{baseUrl}}/mp/draft/update?accountId=1&mediaId=r6ryvl6LrxBU0miaST4Y-q-G9pdsmZw0OYG4FzHQkKfpLfEwIH51wy2bxisx8PvW +Content-Type: application/json +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} + +[{ + "title": "我是标题(OOO)", + "author": "我是作者", + "digest": "我是摘要", + "content": "我是内容", + "contentSourceUrl": "https://www.iocoder.cn", + "thumbMediaId": "r6ryvl6LrxBU0miaST4Y-pIcmK-zAAId-9TGgy-DrSLhjVuWbuT3ZBjk9K1yQ0Dn" +}, { + "title": "我是标题(XXX)", + "author": "我是作者", + "digest": "我是摘要", + "content": "我是内容", + "contentSourceUrl": "https://www.iocoder.cn", + "thumbMediaId": "r6ryvl6LrxBU0miaST4Y-pIcmK-zAAId-9TGgy-DrSLhjVuWbuT3ZBjk9K1yQ0Dn" +}] diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/MpDraftController.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/MpDraftController.java new file mode 100644 index 0000000..d113cef --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/MpDraftController.java @@ -0,0 +1,136 @@ +package cn.iocoder.yudao.module.mp.controller.admin.news; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; +import cn.iocoder.yudao.framework.common.util.object.PageUtils; +import cn.iocoder.yudao.module.mp.controller.admin.news.vo.MpDraftPageReqVO; +import cn.iocoder.yudao.module.mp.dal.dataobject.material.MpMaterialDO; +import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory; +import cn.iocoder.yudao.module.mp.service.material.MpMaterialService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.tags.Tag; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.draft.*; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import jakarta.annotation.Resource; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen; +import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.*; + +@Tag(name = "管理后台 - 公众号草稿") +@RestController +@RequestMapping("/mp/draft") +@Validated +public class MpDraftController { + + @Resource + private MpServiceFactory mpServiceFactory; + + @Resource + private MpMaterialService mpMaterialService; + + @GetMapping("/page") + @Operation(summary = "获得草稿分页") + @PreAuthorize("@ss.hasPermission('mp:draft:query')") + public CommonResult> getDraftPage(MpDraftPageReqVO reqVO) { + // 从公众号查询草稿箱 + WxMpService mpService = mpServiceFactory.getRequiredMpService(reqVO.getAccountId()); + WxMpDraftList draftList; + try { + draftList = mpService.getDraftService().listDraft(PageUtils.getStart(reqVO), reqVO.getPageSize()); + } catch (WxErrorException e) { + throw exception(DRAFT_LIST_FAIL, e.getError().getErrorMsg()); + } + // 查询对应的图片地址。目的:解决公众号的图片链接无法在我们后台展示 + setDraftThumbUrl(draftList.getItems()); + + // 返回分页 + return success(new PageResult<>(draftList.getItems(), draftList.getTotalCount().longValue())); + } + + private void setDraftThumbUrl(List items) { + // 1.1 获得 mediaId 数组 + Set mediaIds = new HashSet<>(); + items.forEach(item -> item.getContent().getNewsItem().forEach(newsItem -> mediaIds.add(newsItem.getThumbMediaId()))); + if (CollUtil.isEmpty(mediaIds)) { + return; + } + // 1.2 批量查询对应的 Media 素材 + Map materials = CollectionUtils.convertMap(mpMaterialService.getMaterialListByMediaId(mediaIds), + MpMaterialDO::getMediaId); + + // 2. 设置回 WxMpDraftItem 记录 + items.forEach(item -> item.getContent().getNewsItem().forEach(newsItem -> + findAndThen(materials, newsItem.getThumbMediaId(), material -> newsItem.setThumbUrl(material.getUrl())))); + } + + @PostMapping("/create") + @Operation(summary = "创建草稿") + @Parameter(name = "accountId", description = "公众号账号的编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('mp:draft:create')") + public CommonResult deleteDraft(@RequestParam("accountId") Long accountId, + @RequestBody WxMpAddDraft draft) { + WxMpService mpService = mpServiceFactory.getRequiredMpService(accountId); + try { + String mediaId = mpService.getDraftService().addDraft(draft); + return success(mediaId); + } catch (WxErrorException e) { + throw exception(DRAFT_CREATE_FAIL, e.getError().getErrorMsg()); + } + } + + @PutMapping("/update") + @Operation(summary = "更新草稿") + @Parameters({ + @Parameter(name = "accountId", description = "公众号账号的编号", required = true, example = "1024"), + @Parameter(name = "mediaId", description = "草稿素材的编号", required = true, example = "xxx") + }) + @PreAuthorize("@ss.hasPermission('mp:draft:update')") + public CommonResult deleteDraft(@RequestParam("accountId") Long accountId, + @RequestParam("mediaId") String mediaId, + @RequestBody List articles) { + WxMpService mpService = mpServiceFactory.getRequiredMpService(accountId); + try { + for (int i = 0; i < articles.size(); i++) { + WxMpDraftArticles article = articles.get(i); + mpService.getDraftService().updateDraft(new WxMpUpdateDraft(mediaId, i, article)); + } + return success(true); + } catch (WxErrorException e) { + throw exception(DRAFT_UPDATE_FAIL, e.getError().getErrorMsg()); + } + } + + @DeleteMapping("/delete") + @Operation(summary = "删除草稿") + @Parameters({ + @Parameter(name = "accountId", description = "公众号账号的编号", required = true, example = "1024"), + @Parameter(name = "mediaId", description = "草稿素材的编号", required = true, example = "xxx") + }) + @PreAuthorize("@ss.hasPermission('mp:draft:delete')") + public CommonResult deleteDraft(@RequestParam("accountId") Long accountId, + @RequestParam("mediaId") String mediaId) { + WxMpService mpService = mpServiceFactory.getRequiredMpService(accountId); + try { + mpService.getDraftService().delDraft(mediaId); + return success(true); + } catch (WxErrorException e) { + throw exception(DRAFT_DELETE_FAIL, e.getError().getErrorMsg()); + } + } + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/MpFreePublishController.http b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/MpFreePublishController.http new file mode 100644 index 0000000..246a95c --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/MpFreePublishController.http @@ -0,0 +1,13 @@ +### 请求 /mp/free-publish/page 接口 => 成功 +GET {{baseUrl}}/mp/free-publish/page?accountId=1&pageNo=1&pageSize=10 +Content-Type: application/json +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} + +### 请求 /mp/free-publish/submit 接口 => 成功 +POST {{baseUrl}}/mp/free-publish/submit?accountId=1&mediaId=r6ryvl6LrxBU0miaST4Y-vilmd7iS51D8IPddxflWrau0hIQ2ovY8YanO5jlgUcM +Content-Type: application/json +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} + +{} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/MpFreePublishController.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/MpFreePublishController.java new file mode 100644 index 0000000..099677f --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/MpFreePublishController.java @@ -0,0 +1,119 @@ +package cn.iocoder.yudao.module.mp.controller.admin.news; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; +import cn.iocoder.yudao.framework.common.util.object.PageUtils; +import cn.iocoder.yudao.module.mp.controller.admin.news.vo.MpFreePublishPageReqVO; +import cn.iocoder.yudao.module.mp.dal.dataobject.material.MpMaterialDO; +import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory; +import cn.iocoder.yudao.module.mp.service.material.MpMaterialService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.tags.Tag; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.freepublish.WxMpFreePublishItem; +import me.chanjar.weixin.mp.bean.freepublish.WxMpFreePublishList; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import jakarta.annotation.Resource; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen; +import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.*; + +@Tag(name = "管理后台 - 公众号发布能力") +@RestController +@RequestMapping("/mp/free-publish") +@Validated +public class MpFreePublishController { + + @Resource + private MpServiceFactory mpServiceFactory; + + @Resource + private MpMaterialService mpMaterialService; + + @GetMapping("/page") + @Operation(summary = "获得已发布的图文分页") + @PreAuthorize("@ss.hasPermission('mp:free-publish:query')") + public CommonResult> getFreePublishPage(MpFreePublishPageReqVO reqVO) { + // 从公众号查询已发布的图文列表 + WxMpService mpService = mpServiceFactory.getRequiredMpService(reqVO.getAccountId()); + WxMpFreePublishList publicationRecords; + try { + publicationRecords = mpService.getFreePublishService().getPublicationRecords( + PageUtils.getStart(reqVO), reqVO.getPageSize()); + } catch (WxErrorException e) { + throw exception(FREE_PUBLISH_LIST_FAIL, e.getError().getErrorMsg()); + } + // 查询对应的图片地址。目的:解决公众号的图片链接无法在我们后台展示 + setFreePublishThumbUrl(publicationRecords.getItems()); + + // 返回分页 + return success(new PageResult<>(publicationRecords.getItems(), publicationRecords.getTotalCount().longValue())); + } + + private void setFreePublishThumbUrl(List items) { + // 1.1 获得 mediaId 数组 + Set mediaIds = new HashSet<>(); + items.forEach(item -> item.getContent().getNewsItem().forEach(newsItem -> mediaIds.add(newsItem.getThumbMediaId()))); + if (CollUtil.isEmpty(mediaIds)) { + return; + } + // 1.2 批量查询对应的 Media 素材 + Map materials = CollectionUtils.convertMap(mpMaterialService.getMaterialListByMediaId(mediaIds), + MpMaterialDO::getMediaId); + + // 2. 设置回 WxMpFreePublishItem 记录 + items.forEach(item -> item.getContent().getNewsItem().forEach(newsItem -> + findAndThen(materials, newsItem.getThumbMediaId(), material -> newsItem.setThumbUrl(material.getUrl())))); + } + + @PostMapping("/submit") + @Operation(summary = "发布草稿") + @Parameters({ + @Parameter(name = "accountId", description = "公众号账号的编号", required = true, example = "1024"), + @Parameter(name = "mediaId", description = "要发布的草稿的 media_id", required = true, example = "2048") + }) + @PreAuthorize("@ss.hasPermission('mp:free-publish:submit')") + public CommonResult submitFreePublish(@RequestParam("accountId") Long accountId, + @RequestParam("mediaId") String mediaId) { + WxMpService mpService = mpServiceFactory.getRequiredMpService(accountId); + try { + String publishId = mpService.getFreePublishService().submit(mediaId); + return success(publishId); + } catch (WxErrorException e) { + throw exception(FREE_PUBLISH_SUBMIT_FAIL, e.getError().getErrorMsg()); + } + } + + @DeleteMapping("/delete") + @Operation(summary = "删除草稿") + @Parameters({ + @Parameter(name = "accountId", description = "公众号账号的编号", required = true, example = "1024"), + @Parameter(name = "articleId", description = "发布记录的编号", required = true, example = "2048") + }) + @PreAuthorize("@ss.hasPermission('mp:free-publish:delete')") + public CommonResult deleteFreePublish(@RequestParam("accountId") Long accountId, + @RequestParam("articleId") String articleId) { + WxMpService mpService = mpServiceFactory.getRequiredMpService(accountId); + try { + mpService.getFreePublishService().deletePushAllArticle(articleId); + return success(true); + } catch (WxErrorException e) { + throw exception(FREE_PUBLISH_DELETE_FAIL, e.getError().getErrorMsg()); + } + } + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/vo/MpDraftPageReqVO.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/vo/MpDraftPageReqVO.java new file mode 100644 index 0000000..3180257 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/vo/MpDraftPageReqVO.java @@ -0,0 +1,21 @@ +package cn.iocoder.yudao.module.mp.controller.admin.news.vo; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import jakarta.validation.constraints.NotNull; + +@Schema(description = "管理后台 - 公众号草稿的分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class MpDraftPageReqVO extends PageParam { + + @Schema(description = "公众号账号的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotNull(message = "公众号账号的编号不能为空") + private Long accountId; + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/vo/MpFreePublishPageReqVO.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/vo/MpFreePublishPageReqVO.java new file mode 100644 index 0000000..9f35edb --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/vo/MpFreePublishPageReqVO.java @@ -0,0 +1,21 @@ +package cn.iocoder.yudao.module.mp.controller.admin.news.vo; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import jakarta.validation.constraints.NotNull; + +@Schema(description = "管理后台 - 公众号已发布列表的分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class MpFreePublishPageReqVO extends PageParam { + + @Schema(description = "公众号账号的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotNull(message = "公众号账号的编号不能为空") + private Long accountId; + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/open/MpOpenController.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/open/MpOpenController.java new file mode 100644 index 0000000..d1a1e3f --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/open/MpOpenController.java @@ -0,0 +1,117 @@ +package cn.iocoder.yudao.module.mp.controller.admin.open; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; +import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; +import cn.iocoder.yudao.module.mp.controller.admin.open.vo.MpOpenCheckSignatureReqVO; +import cn.iocoder.yudao.module.mp.controller.admin.open.vo.MpOpenHandleMessageReqVO; +import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; +import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory; +import cn.iocoder.yudao.module.mp.framework.mp.core.context.MpContextHolder; +import cn.iocoder.yudao.module.mp.service.account.MpAccountService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +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.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import jakarta.annotation.Resource; +import java.util.Objects; + +@Tag(name = "管理后台 - 公众号回调") +@RestController +@RequestMapping("/mp/open") +@Validated +@Slf4j +public class MpOpenController { + + @Resource + private MpServiceFactory mpServiceFactory; + + @Resource + private MpAccountService mpAccountService; + + /** + * 接收微信公众号的消息推送 + * + * 文档 + */ + @Operation(summary = "处理消息") + @PostMapping(value = "/{appId}", produces = "application/xml; charset=UTF-8") + @TenantIgnore + public String handleMessage(@PathVariable("appId") String appId, + @RequestBody String content, + MpOpenHandleMessageReqVO reqVO) { + log.info("[handleMessage][appId({}) 推送消息,参数({}) 内容({})]", appId, reqVO, content); + + // 处理 appId + 多租户的上下文 + MpAccountDO account = mpAccountService.getAccountFromCache(appId); + Assert.notNull(account, "公众号 appId({}) 不存在", appId); + try { + MpContextHolder.setAppId(appId); + return TenantUtils.execute(account.getTenantId(), + () -> handleMessage0(appId, content, reqVO)); + } finally { + MpContextHolder.clear(); + } + } + + /** + * 接收微信公众号的校验签名 + * + * 对应 文档 + */ + @Operation(summary = "校验签名") // 参见 + @GetMapping(value = "/{appId}", produces = "text/plain;charset=utf-8") + @TenantIgnore + public String checkSignature(@PathVariable("appId") String appId, + MpOpenCheckSignatureReqVO reqVO) { + log.info("[checkSignature][appId({}) 接收到来自微信服务器的认证消息({})]", appId, reqVO); + // 校验请求签名 + WxMpService wxMpService = mpServiceFactory.getRequiredMpService(appId); + // 校验通过 + if (wxMpService.checkSignature(reqVO.getTimestamp(), reqVO.getNonce(), reqVO.getSignature())) { + return reqVO.getEchostr(); + } + // 校验不通过 + return "非法请求"; + } + + private String handleMessage0(String appId, String content, MpOpenHandleMessageReqVO reqVO) { + // 校验请求签名 + WxMpService mppService = mpServiceFactory.getRequiredMpService(appId); + Assert.isTrue(mppService.checkSignature(reqVO.getTimestamp(), reqVO.getNonce(), reqVO.getSignature()), + "非法请求"); + + // 第一步,解析消息 + WxMpXmlMessage inMessage = null; + if (StrUtil.isBlank(reqVO.getEncrypt_type())) { // 明文模式 + inMessage = WxMpXmlMessage.fromXml(content); + } else if (Objects.equals(reqVO.getEncrypt_type(), MpOpenHandleMessageReqVO.ENCRYPT_TYPE_AES)) { // AES 加密模式 + inMessage = WxMpXmlMessage.fromEncryptedXml(content, mppService.getWxMpConfigStorage(), + reqVO.getTimestamp(), reqVO.getNonce(), reqVO.getMsg_signature()); + } + Assert.notNull(inMessage, "消息解析失败,原因:消息为空"); + + // 第二步,处理消息 + WxMpMessageRouter mpMessageRouter = mpServiceFactory.getRequiredMpMessageRouter(appId); + WxMpXmlOutMessage outMessage = mpMessageRouter.route(inMessage); + if (outMessage == null) { + return ""; + } + + // 第三步,返回消息 + if (StrUtil.isBlank(reqVO.getEncrypt_type())) { // 明文模式 + return outMessage.toXml(); + } else if (Objects.equals(reqVO.getEncrypt_type(), MpOpenHandleMessageReqVO.ENCRYPT_TYPE_AES)) { // AES 加密模式 + return outMessage.toEncryptedXml(mppService.getWxMpConfigStorage()); + } + return ""; + } + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/open/vo/MpOpenCheckSignatureReqVO.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/open/vo/MpOpenCheckSignatureReqVO.java new file mode 100644 index 0000000..0dd9953 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/open/vo/MpOpenCheckSignatureReqVO.java @@ -0,0 +1,29 @@ +package cn.iocoder.yudao.module.mp.controller.admin.open.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import jakarta.validation.constraints.NotEmpty; + +@Schema(description = "管理后台 - 公众号校验签名 Request VO") +@Data +public class MpOpenCheckSignatureReqVO { + + @Schema(description = "微信加密签名", requiredMode = Schema.RequiredMode.REQUIRED, example = "490eb57f448b87bd5f20ccef58aa4de46aa1908e") + @NotEmpty(message = "微信加密签名不能为空") + private String signature; + + @Schema(description = "时间戳", requiredMode = Schema.RequiredMode.REQUIRED, example = "1672587863") + @NotEmpty(message = "时间戳不能为空") + private String timestamp; + + @Schema(description = "随机数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1827365808") + @NotEmpty(message = "随机数不能为空") + private String nonce; + + @Schema(description = "随机字符串", requiredMode = Schema.RequiredMode.REQUIRED, example = "2721154047828672511") + @NotEmpty(message = "随机字符串不能为空") + @SuppressWarnings("SpellCheckingInspection") + private String echostr; + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/open/vo/MpOpenHandleMessageReqVO.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/open/vo/MpOpenHandleMessageReqVO.java new file mode 100644 index 0000000..11caca0 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/open/vo/MpOpenHandleMessageReqVO.java @@ -0,0 +1,37 @@ +package cn.iocoder.yudao.module.mp.controller.admin.open.vo; + + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import jakarta.validation.constraints.NotEmpty; + +@Schema(description = "管理后台 - 公众号处理消息 Request VO") +@Data +public class MpOpenHandleMessageReqVO { + + public static final String ENCRYPT_TYPE_AES = "aes"; + + @Schema(description = "微信加密签名", requiredMode = Schema.RequiredMode.REQUIRED, example = "490eb57f448b87bd5f20ccef58aa4de46aa1908e") + @NotEmpty(message = "微信加密签名不能为空") + private String signature; + + @Schema(description = "时间戳", requiredMode = Schema.RequiredMode.REQUIRED, example = "1672587863") + @NotEmpty(message = "时间戳不能为空") + private String timestamp; + + @Schema(description = "随机数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1827365808") + @NotEmpty(message = "随机数不能为空") + private String nonce; + + @Schema(description = "粉丝 openid", requiredMode = Schema.RequiredMode.REQUIRED, example = "oz-Jdtyn-WGm4C4I5Z-nvBMO_ZfY") + @NotEmpty(message = "粉丝 openid 不能为空") + private String openid; + + @Schema(description = "消息加密类型", example = "aes") + private String encrypt_type; + + @Schema(description = "微信签名", example = "QW5kcm9pZCBUaGUgQmFzZTY0IGlzIGEgZ2VuZXJhdGVkIHN0cmluZw==") + private String msg_signature; + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/MpStatisticsController.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/MpStatisticsController.java new file mode 100644 index 0000000..6c5016e --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/MpStatisticsController.java @@ -0,0 +1,68 @@ +package cn.iocoder.yudao.module.mp.controller.admin.statistics; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.module.mp.controller.admin.statistics.vo.*; +import cn.iocoder.yudao.module.mp.convert.statistics.MpStatisticsConvert; +import cn.iocoder.yudao.module.mp.service.statistics.MpStatisticsService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import me.chanjar.weixin.mp.bean.datacube.WxDataCubeInterfaceResult; +import me.chanjar.weixin.mp.bean.datacube.WxDataCubeMsgResult; +import me.chanjar.weixin.mp.bean.datacube.WxDataCubeUserCumulate; +import me.chanjar.weixin.mp.bean.datacube.WxDataCubeUserSummary; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import jakarta.annotation.Resource; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 公众号统计") +@RestController +@RequestMapping("/mp/statistics") +@Validated +public class MpStatisticsController { + + @Resource + private MpStatisticsService mpStatisticsService; + + @GetMapping("/user-summary") + @Operation(summary = "获得粉丝增减数据") + @PreAuthorize("@ss.hasPermission('mp:statistics:query')") + public CommonResult> getUserSummary(MpStatisticsGetReqVO getReqVO) { + List list = mpStatisticsService.getUserSummary( + getReqVO.getAccountId(), getReqVO.getDate()); + return success(MpStatisticsConvert.INSTANCE.convertList01(list)); + } + + @GetMapping("/user-cumulate") + @Operation(summary = "获得粉丝累计数据") + @PreAuthorize("@ss.hasPermission('mp:statistics:query')") + public CommonResult> getUserCumulate(MpStatisticsGetReqVO getReqVO) { + List list = mpStatisticsService.getUserCumulate( + getReqVO.getAccountId(), getReqVO.getDate()); + return success(MpStatisticsConvert.INSTANCE.convertList02(list)); + } + + @GetMapping("/upstream-message") + @Operation(summary = "获取消息发送概况数据") + @PreAuthorize("@ss.hasPermission('mp:statistics:query')") + public CommonResult> getUpstreamMessage(MpStatisticsGetReqVO getReqVO) { + List list = mpStatisticsService.getUpstreamMessage( + getReqVO.getAccountId(), getReqVO.getDate()); + return success(MpStatisticsConvert.INSTANCE.convertList03(list)); + } + + @GetMapping("/interface-summary") + @Operation(summary = "获取消息发送概况数据") + @PreAuthorize("@ss.hasPermission('mp:statistics:query')") + public CommonResult> getInterfaceSummary(MpStatisticsGetReqVO getReqVO) { + List list = mpStatisticsService.getInterfaceSummary( + getReqVO.getAccountId(), getReqVO.getDate()); + return success(MpStatisticsConvert.INSTANCE.convertList04(list)); + } +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsGetReqVO.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsGetReqVO.java new file mode 100644 index 0000000..be8bca0 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsGetReqVO.java @@ -0,0 +1,25 @@ +package cn.iocoder.yudao.module.mp.controller.admin.statistics.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - 获得统计数据 Request VO") +@Data +public class MpStatisticsGetReqVO { + + @Schema(description = "公众号账号的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotNull(message = "公众号账号的编号不能为空") + private Long accountId; + + @Schema(description = "查询时间范围", example = "[2022-07-01 00:00:00, 2022-07-01 23:59:59]") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @NotNull(message = "查询时间范围不能为空") + private LocalDateTime[] date; + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsInterfaceSummaryRespVO.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsInterfaceSummaryRespVO.java new file mode 100644 index 0000000..c7952f4 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsInterfaceSummaryRespVO.java @@ -0,0 +1,27 @@ +package cn.iocoder.yudao.module.mp.controller.admin.statistics.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 某一天的接口分析数据 Response VO") +@Data +public class MpStatisticsInterfaceSummaryRespVO { + + @Schema(description = "日期", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime refDate; + + @Schema(description = "通过服务器配置地址获得消息后,被动回复粉丝消息的次数", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") + private Integer callbackCount; + + @Schema(description = "上述动作的失败次数", requiredMode = Schema.RequiredMode.REQUIRED, example = "20") + private Integer failCount; + + @Schema(description = "总耗时,除以 callback_count 即为平均耗时", requiredMode = Schema.RequiredMode.REQUIRED, example = "30") + private Integer totalTimeCost; + + @Schema(description = "最大耗时", requiredMode = Schema.RequiredMode.REQUIRED, example = "40") + private Integer maxTimeCost; + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsUpstreamMessageRespVO.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsUpstreamMessageRespVO.java new file mode 100644 index 0000000..f9bba2c --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsUpstreamMessageRespVO.java @@ -0,0 +1,21 @@ +package cn.iocoder.yudao.module.mp.controller.admin.statistics.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 某一天的粉丝增减数据 Response VO") +@Data +public class MpStatisticsUpstreamMessageRespVO { + + @Schema(description = "日期", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime refDate; + + @Schema(description = "上行发送了(向公众号发送了)消息的粉丝数", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") + private Integer messageUser; + + @Schema(description = "上行发送了消息的消息总数", requiredMode = Schema.RequiredMode.REQUIRED, example = "20") + private Integer messageCount; + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsUserCumulateRespVO.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsUserCumulateRespVO.java new file mode 100644 index 0000000..6503c07 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsUserCumulateRespVO.java @@ -0,0 +1,18 @@ +package cn.iocoder.yudao.module.mp.controller.admin.statistics.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 某一天的消息发送概况数据 Response VO") +@Data +public class MpStatisticsUserCumulateRespVO { + + @Schema(description = "日期", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime refDate; + + @Schema(description = "累计粉丝量", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") + private Integer cumulateUser; + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsUserSummaryRespVO.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsUserSummaryRespVO.java new file mode 100644 index 0000000..7a5373c --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsUserSummaryRespVO.java @@ -0,0 +1,24 @@ +package cn.iocoder.yudao.module.mp.controller.admin.statistics.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 某一天的粉丝增减数据 Response VO") +@Data +public class MpStatisticsUserSummaryRespVO { + + @Schema(description = "日期", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime refDate; + + @Schema(description = "粉丝来源", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + private Integer userSource; + + @Schema(description = "新关注的粉丝数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") + private Integer newUser; + + @Schema(description = "取消关注的粉丝数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "20") + private Integer cancelUser; + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/MpTagController.http b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/MpTagController.http new file mode 100644 index 0000000..a888145 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/MpTagController.http @@ -0,0 +1,39 @@ +### 请求 /mp/tag/create 接口 => 成功 +POST {{baseUrl}}/mp/tag/create +Content-Type: application/json +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} + +{ + "accountId": "1", + "name": "测试" +} + +### 请求 /mp/tag/update 接口 => 成功 +PUT {{baseUrl}}/mp/tag/update +Content-Type: application/json +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} + +{ + "id": "3", + "name": "测试标签啦" +} + +### 请求 /mp/tag/delete 接口 => 成功 +DELETE {{baseUrl}}/mp/tag/delete?id=3 +Content-Type: application/json +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} + +### 请求 /mp/tag/page 接口 => 成功 +GET {{baseUrl}}/mp/tag/page?accountId=1&pageNo=1&pageSize=10 +Content-Type: application/json +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} + +### 请求 /mp/tag/sync 接口 => 成功 +POST {{baseUrl}}/mp/tag/sync?accountId=1 +Content-Type: application/json +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/MpTagController.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/MpTagController.java new file mode 100644 index 0000000..dae24ff --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/MpTagController.java @@ -0,0 +1,88 @@ +package cn.iocoder.yudao.module.mp.controller.admin.tag; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.mp.controller.admin.tag.vo.*; +import cn.iocoder.yudao.module.mp.convert.tag.MpTagConvert; +import cn.iocoder.yudao.module.mp.dal.dataobject.tag.MpTagDO; +import cn.iocoder.yudao.module.mp.service.tag.MpTagService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 公众号标签") +@RestController +@RequestMapping("/mp/tag") +@Validated +public class MpTagController { + + @Resource + private MpTagService mpTagService; + + @PostMapping("/create") + @Operation(summary = "创建公众号标签") + @PreAuthorize("@ss.hasPermission('mp:tag:create')") + public CommonResult createTag(@Valid @RequestBody MpTagCreateReqVO createReqVO) { + return success(mpTagService.createTag(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新公众号标签") + @PreAuthorize("@ss.hasPermission('mp:tag:update')") + public CommonResult updateTag(@Valid @RequestBody MpTagUpdateReqVO updateReqVO) { + mpTagService.updateTag(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除公众号标签") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('mp:tag:delete')") + public CommonResult deleteTag(@RequestParam("id") Long id) { + mpTagService.deleteTag(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获取公众号标签详情") + @PreAuthorize("@ss.hasPermission('mp:tag:query')") + public CommonResult get(@RequestParam("id") Long id) { + MpTagDO mpTagDO = mpTagService.get(id); + return success(MpTagConvert.INSTANCE.convert(mpTagDO)); + } + + @GetMapping("/page") + @Operation(summary = "获取公众号标签分页") + @PreAuthorize("@ss.hasPermission('mp:tag:query')") + public CommonResult> getTagPage(MpTagPageReqVO pageReqVO) { + PageResult pageResult = mpTagService.getTagPage(pageReqVO); + return success(MpTagConvert.INSTANCE.convertPage(pageResult)); + } + + @GetMapping("/list-all-simple") + @Operation(summary = "获取公众号账号精简信息列表") + @PreAuthorize("@ss.hasPermission('mp:account:query')") + public CommonResult> getSimpleTags() { + List list = mpTagService.getTagList(); + return success(MpTagConvert.INSTANCE.convertList02(list)); + } + + @PostMapping("/sync") + @Operation(summary = "同步公众号标签") + @Parameter(name = "accountId", description = "公众号账号的编号", required = true) + @PreAuthorize("@ss.hasPermission('mp:tag:sync')") + public CommonResult syncTag(@RequestParam("accountId") Long accountId) { + mpTagService.syncTag(accountId); + return success(true); + } + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagBaseVO.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagBaseVO.java new file mode 100644 index 0000000..68c4ae9 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagBaseVO.java @@ -0,0 +1,21 @@ +package cn.iocoder.yudao.module.mp.controller.admin.tag.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import jakarta.validation.constraints.NotEmpty; + +/** + * 公众号标签 Base VO,提供给添加、修改、详细的子 VO 使用 + * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 + * + * @author fengdan + */ +@Data +public class MpTagBaseVO { + + @Schema(description = "标签名", requiredMode = Schema.RequiredMode.REQUIRED, example = "土豆") + @NotEmpty(message = "标签名不能为空") + private String name; + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagCreateReqVO.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagCreateReqVO.java new file mode 100644 index 0000000..0236dbf --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagCreateReqVO.java @@ -0,0 +1,20 @@ +package cn.iocoder.yudao.module.mp.controller.admin.tag.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import jakarta.validation.constraints.NotNull; + +@Schema(description = "管理后台 - 公众号标签创建 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class MpTagCreateReqVO extends MpTagBaseVO { + + @Schema(description = "公众号账号的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") + @NotNull(message = "公众号账号的编号不能为空") + private Long accountId; + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagPageReqVO.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagPageReqVO.java new file mode 100644 index 0000000..b89cbc5 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagPageReqVO.java @@ -0,0 +1,24 @@ +package cn.iocoder.yudao.module.mp.controller.admin.tag.vo; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import jakarta.validation.constraints.NotEmpty; + +@Schema(description = "管理后台 - 公众号标签分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class MpTagPageReqVO extends PageParam { + + @Schema(description = "公众号账号的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") + @NotEmpty(message = "公众号账号的编号不能为空") + private Long accountId; + + @Schema(description = "标签名,模糊匹配", example = "哈哈") + private String name; + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagRespVO.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagRespVO.java new file mode 100644 index 0000000..0da562d --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagRespVO.java @@ -0,0 +1,25 @@ +package cn.iocoder.yudao.module.mp.controller.admin.tag.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 公众号标签 Response VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class MpTagRespVO extends MpTagBaseVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "此标签下粉丝数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + private Integer count; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagSimpleRespVO.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagSimpleRespVO.java new file mode 100644 index 0000000..ca23be1 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagSimpleRespVO.java @@ -0,0 +1,19 @@ +package cn.iocoder.yudao.module.mp.controller.admin.tag.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 公众号标签精简信息 Response VO") +@Data +public class MpTagSimpleRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "公众号的标签编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") + private Long tagId; + + @Schema(description = "标签名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "快乐") + private String name; + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagUpdateReqVO.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagUpdateReqVO.java new file mode 100644 index 0000000..9710cd9 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagUpdateReqVO.java @@ -0,0 +1,20 @@ +package cn.iocoder.yudao.module.mp.controller.admin.tag.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import jakarta.validation.constraints.NotNull; + +@Schema(description = "管理后台 - 公众号标签更新 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class MpTagUpdateReqVO extends MpTagBaseVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "编号不能为空") + private Long id; + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/MpUserController.http b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/MpUserController.http new file mode 100644 index 0000000..c78356a --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/MpUserController.http @@ -0,0 +1,18 @@ +### 请求 /mp/user/sync 接口 => 成功 +POST {{baseUrl}}/mp/user/sync?accountId=1 +Content-Type: application/json +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} + +### 请求 /mp/user/update 接口 => 成功 +PUT {{baseUrl}}/mp/user/update +Content-Type: application/json +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} + +{ + "id": "3", + "nickname": "test", + "remark": "测试备注", + "tagIds": [103, 104] +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/MpUserController.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/MpUserController.java new file mode 100644 index 0000000..4da1cf9 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/MpUserController.java @@ -0,0 +1,65 @@ +package cn.iocoder.yudao.module.mp.controller.admin.user; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.mp.controller.admin.user.vo.MpUserPageReqVO; +import cn.iocoder.yudao.module.mp.controller.admin.user.vo.MpUserRespVO; +import cn.iocoder.yudao.module.mp.controller.admin.user.vo.MpUserUpdateReqVO; +import cn.iocoder.yudao.module.mp.convert.user.MpUserConvert; +import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO; +import cn.iocoder.yudao.module.mp.service.user.MpUserService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import jakarta.annotation.Resource; +import jakarta.validation.Valid; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 公众号粉丝") +@RestController +@RequestMapping("/mp/user") +@Validated +public class MpUserController { + + @Resource + private MpUserService mpUserService; + + @GetMapping("/page") + @Operation(summary = "获得公众号粉丝分页") + @PreAuthorize("@ss.hasPermission('mp:user:query')") + public CommonResult> getUserPage(@Valid MpUserPageReqVO pageVO) { + PageResult pageResult = mpUserService.getUserPage(pageVO); + return success(MpUserConvert.INSTANCE.convertPage(pageResult)); + } + + @GetMapping("/get") + @Operation(summary = "获得公众号粉丝") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('mp:user:query')") + public CommonResult getUser(@RequestParam("id") Long id) { + return success(MpUserConvert.INSTANCE.convert(mpUserService.getUser(id))); + } + + @PutMapping("/update") + @Operation(summary = "更新公众号粉丝") + @PreAuthorize("@ss.hasPermission('mp:user:update')") + public CommonResult updateUser(@Valid @RequestBody MpUserUpdateReqVO updateReqVO) { + mpUserService.updateUser(updateReqVO); + return success(true); + } + + @PostMapping("/sync") + @Operation(summary = "同步公众号粉丝") + @Parameter(name = "accountId", description = "公众号账号的编号", required = true) + @PreAuthorize("@ss.hasPermission('mp:user:sync')") + public CommonResult syncUser(@RequestParam("accountId") Long accountId) { + mpUserService.syncUser(accountId); + return success(true); + } + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/vo/MpUserPageReqVO.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/vo/MpUserPageReqVO.java new file mode 100644 index 0000000..66f45f1 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/vo/MpUserPageReqVO.java @@ -0,0 +1,30 @@ +package cn.iocoder.yudao.module.mp.controller.admin.user.vo; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import jakarta.validation.constraints.NotNull; + +@Schema(description = "管理后台 - 公众号粉丝分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class MpUserPageReqVO extends PageParam { + + @Schema(description = "公众号账号的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") + @NotNull(message = "公众号账号的编号不能为空") + private Long accountId; + + @Schema(description = "公众号粉丝标识,模糊匹配", example = "o6_bmjrPTlm6_2sgVt7hMZOPfL2M") + private String openid; + + @Schema(description = "微信生态唯一标识,模糊匹配", example = "o6_bmjrPTlm6_2sgVt7hMZOPfL2M") + private String unionId; + + @Schema(description = "公众号粉丝昵称,模糊匹配", example = "芋艿") + private String nickname; + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/vo/MpUserRespVO.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/vo/MpUserRespVO.java new file mode 100644 index 0000000..95f919a --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/vo/MpUserRespVO.java @@ -0,0 +1,55 @@ +package cn.iocoder.yudao.module.mp.controller.admin.user.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "管理后台 - 公众号粉丝 Response VO") +@Data +public class MpUserRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "公众号粉丝标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "o6_bmjrPTlm6_2sgVt7hMZOPfL2M") + private String openid; + + @Schema(description = "微信生态唯一标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "o6_bmjrPTlm6_2sgVt7hMZOPfL2M") + private String unionId; + + @Schema(description = "关注状态 参见 CommonStatusEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer subscribeStatus; + @Schema(description = "关注时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime subscribeTime; + @Schema(description = "取消关注时间") + private LocalDateTime unsubscribeTime; + + @Schema(description = "昵称", example = "芋道") + private String nickname; + @Schema(description = "头像地址", example = "https://www.iocoder.cn/1.png") + private String headImageUrl; + @Schema(description = "语言", example = "zh_CN") + private String language; + @Schema(description = "国家", example = "中国") + private String country; + @Schema(description = "省份", example = "广东省") + private String province; + @Schema(description = "城市", example = "广州市") + private String city; + @Schema(description = "备注", example = "你是一个芋头嘛") + private String remark; + + @Schema(description = "标签编号数组", example = "1,2,3") + private List tagIds; + + @Schema(description = "公众号账号的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Long accountId; + @Schema(description = "公众号账号的 appId", requiredMode = Schema.RequiredMode.REQUIRED, example = "wx1234567890") + private String appId; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/vo/MpUserUpdateReqVO.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/vo/MpUserUpdateReqVO.java new file mode 100644 index 0000000..490287e --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/vo/MpUserUpdateReqVO.java @@ -0,0 +1,26 @@ +package cn.iocoder.yudao.module.mp.controller.admin.user.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import jakarta.validation.constraints.NotNull; +import java.util.List; + +@Schema(description = "管理后台 - 公众号粉丝更新 Request VO") +@Data +public class MpUserUpdateReqVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotNull(message = "编号不能为空") + private Long id; + + @Schema(description = "昵称", example = "芋道") + private String nickname; + + @Schema(description = "备注", example = "你是一个芋头嘛") + private String remark; + + @Schema(description = "标签编号数组", example = "1,2,3") + private List tagIds; + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/package-info.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/package-info.java new file mode 100644 index 0000000..31a0577 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/package-info.java @@ -0,0 +1,6 @@ +/** + * 提供 RESTful API 给前端: + * 1. admin 包:提供给管理后台 yudao-ui-admin 前端项目 + * 2. app 包:提供给用户 APP yudao-ui-app 前端项目,它的 Controller 和 VO 都要添加 App 前缀,用于和管理后台进行区分 + */ +package cn.iocoder.yudao.module.mp.controller; diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/convert/account/MpAccountConvert.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/convert/account/MpAccountConvert.java new file mode 100644 index 0000000..5f8fde3 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/convert/account/MpAccountConvert.java @@ -0,0 +1,31 @@ +package cn.iocoder.yudao.module.mp.convert.account; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountCreateReqVO; +import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountRespVO; +import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountSimpleRespVO; +import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountUpdateReqVO; +import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.util.List; + +@Mapper +public interface MpAccountConvert { + + MpAccountConvert INSTANCE = Mappers.getMapper(MpAccountConvert.class); + + MpAccountDO convert(MpAccountCreateReqVO bean); + + MpAccountDO convert(MpAccountUpdateReqVO bean); + + MpAccountRespVO convert(MpAccountDO bean); + + List convertList(List list); + + PageResult convertPage(PageResult page); + + List convertList02(List list); + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/convert/material/MpMaterialConvert.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/convert/material/MpMaterialConvert.java new file mode 100644 index 0000000..ca4f69c --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/convert/material/MpMaterialConvert.java @@ -0,0 +1,47 @@ +package cn.iocoder.yudao.module.mp.convert.material; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.mp.controller.admin.material.vo.MpMaterialRespVO; +import cn.iocoder.yudao.module.mp.controller.admin.material.vo.MpMaterialUploadRespVO; +import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; +import cn.iocoder.yudao.module.mp.dal.dataobject.material.MpMaterialDO; +import me.chanjar.weixin.mp.bean.material.WxMpMaterial; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.factory.Mappers; + +import java.io.File; + +@Mapper +public interface MpMaterialConvert { + + MpMaterialConvert INSTANCE = Mappers.getMapper(MpMaterialConvert.class); + + @Mappings({ + @Mapping(target = "id", ignore = true), + @Mapping(source = "account.id", target = "accountId"), + @Mapping(source = "account.appId", target = "appId"), + @Mapping(source = "name", target = "name") + }) + MpMaterialDO convert(String mediaId, String type, String url, MpAccountDO account, + String name); + + @Mappings({ + @Mapping(target = "id", ignore = true), + @Mapping(source = "account.id", target = "accountId"), + @Mapping(source = "account.appId", target = "appId"), + @Mapping(source = "name", target = "name") + }) + MpMaterialDO convert(String mediaId, String type, String url, MpAccountDO account, + String name, String title, String introduction, String mpUrl); + + MpMaterialUploadRespVO convert(MpMaterialDO bean); + + default WxMpMaterial convert(String name, File file, String title, String introduction) { + return new WxMpMaterial(name, file, title, introduction); + } + + PageResult convertPage(PageResult page); + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/convert/menu/MpMenuConvert.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/convert/menu/MpMenuConvert.java new file mode 100644 index 0000000..a5ae32a --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/convert/menu/MpMenuConvert.java @@ -0,0 +1,50 @@ +package cn.iocoder.yudao.module.mp.convert.menu; + +import cn.iocoder.yudao.module.mp.controller.admin.menu.vo.MpMenuRespVO; +import cn.iocoder.yudao.module.mp.controller.admin.menu.vo.MpMenuSaveReqVO; +import cn.iocoder.yudao.module.mp.dal.dataobject.menu.MpMenuDO; +import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO; +import me.chanjar.weixin.common.bean.menu.WxMenuButton; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.factory.Mappers; + +import java.util.List; + +@Mapper +public interface MpMenuConvert { + + MpMenuConvert INSTANCE = Mappers.getMapper(MpMenuConvert.class); + + MpMenuRespVO convert(MpMenuDO bean); + + List convertList(List list); + + @Mappings({ + @Mapping(source = "menu.appId", target = "appId"), + @Mapping(source = "menu.replyMessageType", target = "type"), + @Mapping(source = "menu.replyContent", target = "content"), + @Mapping(source = "menu.replyMediaId", target = "mediaId"), + @Mapping(source = "menu.replyThumbMediaId", target = "thumbMediaId"), + @Mapping(source = "menu.replyTitle", target = "title"), + @Mapping(source = "menu.replyDescription", target = "description"), + @Mapping(source = "menu.replyArticles", target = "articles"), + @Mapping(source = "menu.replyMusicUrl", target = "musicUrl"), + @Mapping(source = "menu.replyHqMusicUrl", target = "hqMusicUrl"), + }) + MpMessageSendOutReqBO convert(String openid, MpMenuDO menu); + + List convert(List list); + + @Mappings({ + @Mapping(source = "menuKey", target = "key"), + @Mapping(source = "children", target = "subButtons"), + @Mapping(source = "miniProgramAppId", target = "appId"), + @Mapping(source = "miniProgramPagePath", target = "pagePath"), + }) + WxMenuButton convert(MpMenuSaveReqVO.Menu bean); + + MpMenuDO convert02(MpMenuSaveReqVO.Menu menu); + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/convert/message/MpAutoReplyConvert.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/convert/message/MpAutoReplyConvert.java new file mode 100644 index 0000000..c7cf890 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/convert/message/MpAutoReplyConvert.java @@ -0,0 +1,37 @@ +package cn.iocoder.yudao.module.mp.convert.message; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.mp.controller.admin.message.vo.autoreply.MpAutoReplyCreateReqVO; +import cn.iocoder.yudao.module.mp.controller.admin.message.vo.autoreply.MpAutoReplyRespVO; +import cn.iocoder.yudao.module.mp.controller.admin.message.vo.autoreply.MpAutoReplyUpdateReqVO; +import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpAutoReplyDO; +import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface MpAutoReplyConvert { + + MpAutoReplyConvert INSTANCE = Mappers.getMapper(MpAutoReplyConvert.class); + + @Mappings({ + @Mapping(source = "reply.appId", target = "appId"), + @Mapping(source = "reply.responseMessageType", target = "type"), + @Mapping(source = "reply.responseContent", target = "content"), + @Mapping(source = "reply.responseMediaId", target = "mediaId"), + @Mapping(source = "reply.responseTitle", target = "title"), + @Mapping(source = "reply.responseDescription", target = "description"), + @Mapping(source = "reply.responseArticles", target = "articles"), + }) + MpMessageSendOutReqBO convert(String openid, MpAutoReplyDO reply); + + PageResult convertPage(PageResult page); + + MpAutoReplyRespVO convert(MpAutoReplyDO bean); + + MpAutoReplyDO convert(MpAutoReplyCreateReqVO bean); + + MpAutoReplyDO convert(MpAutoReplyUpdateReqVO bean); +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/convert/message/MpMessageConvert.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/convert/message/MpMessageConvert.java new file mode 100644 index 0000000..8f31a68 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/convert/message/MpMessageConvert.java @@ -0,0 +1,172 @@ +package cn.iocoder.yudao.module.mp.convert.message; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.mp.controller.admin.message.vo.message.MpMessageRespVO; +import cn.iocoder.yudao.module.mp.controller.admin.message.vo.message.MpMessageSendReqVO; +import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; +import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO; +import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO; +import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO; +import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.mp.bean.kefu.WxMpKefuMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutNewsMessage; +import me.chanjar.weixin.mp.builder.outxml.BaseBuilder; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.factory.Mappers; + +import java.util.List; + +@Mapper +public interface MpMessageConvert { + + MpMessageConvert INSTANCE = Mappers.getMapper(MpMessageConvert.class); + + MpMessageRespVO convert(MpMessageDO bean); + + List convertList(List list); + + PageResult convertPage(PageResult page); + + default MpMessageDO convert(WxMpXmlMessage wxMessage, MpAccountDO account, MpUserDO user) { + MpMessageDO message = convert(wxMessage); + if (account != null) { + message.setAccountId(account.getId()).setAppId(account.getAppId()); + } + if (user != null) { + message.setUserId(user.getId()).setOpenid(user.getOpenid()); + } + return message; + } + @Mappings(value = { + @Mapping(source = "msgType", target = "type"), + @Mapping(target = "createTime", ignore = true), + }) + MpMessageDO convert(WxMpXmlMessage bean); + + default MpMessageDO convert(MpMessageSendOutReqBO sendReqBO, MpAccountDO account, MpUserDO user) { + // 构建消息 + MpMessageDO message = new MpMessageDO(); + message.setType(sendReqBO.getType()); + switch (sendReqBO.getType()) { + case WxConsts.XmlMsgType.TEXT: // 1. 文本 + message.setContent(sendReqBO.getContent()); + break; + case WxConsts.XmlMsgType.IMAGE: // 2. 图片 + case WxConsts.XmlMsgType.VOICE: // 3. 语音 + message.setMediaId(sendReqBO.getMediaId()); + break; + case WxConsts.XmlMsgType.VIDEO: // 4. 视频 + message.setMediaId(sendReqBO.getMediaId()) + .setTitle(sendReqBO.getTitle()).setDescription(sendReqBO.getDescription()); + break; + case WxConsts.XmlMsgType.NEWS: // 5. 图文 + message.setArticles(sendReqBO.getArticles()); + case WxConsts.XmlMsgType.MUSIC: // 6. 音乐 + message.setTitle(sendReqBO.getTitle()).setDescription(sendReqBO.getDescription()) + .setMusicUrl(sendReqBO.getMusicUrl()).setHqMusicUrl(sendReqBO.getHqMusicUrl()) + .setThumbMediaId(sendReqBO.getThumbMediaId()); + break; + default: + throw new IllegalArgumentException("不支持的消息类型:" + message.getType()); + } + + // 其它字段 + if (account != null) { + message.setAccountId(account.getId()).setAppId(account.getAppId()); + } + if (user != null) { + message.setUserId(user.getId()).setOpenid(user.getOpenid()); + } + return message; + } + + default WxMpXmlOutMessage convert02(MpMessageDO message, MpAccountDO account) { + BaseBuilder builder; + // 个性化字段 + switch (message.getType()) { + case WxConsts.XmlMsgType.TEXT: + builder = WxMpXmlOutMessage.TEXT().content(message.getContent()); + break; + case WxConsts.XmlMsgType.IMAGE: + builder = WxMpXmlOutMessage.IMAGE().mediaId(message.getMediaId()); + break; + case WxConsts.XmlMsgType.VOICE: + builder = WxMpXmlOutMessage.VOICE().mediaId(message.getMediaId()); + break; + case WxConsts.XmlMsgType.VIDEO: + builder = WxMpXmlOutMessage.VIDEO().mediaId(message.getMediaId()) + .title(message.getTitle()).description(message.getDescription()); + break; + case WxConsts.XmlMsgType.NEWS: + builder = WxMpXmlOutMessage.NEWS().articles(convertList02(message.getArticles())); + break; + case WxConsts.XmlMsgType.MUSIC: + builder = WxMpXmlOutMessage.MUSIC().title(message.getTitle()).description(message.getDescription()) + .musicUrl(message.getMusicUrl()).hqMusicUrl(message.getHqMusicUrl()) + .thumbMediaId(message.getThumbMediaId()); + break; + default: + throw new IllegalArgumentException("不支持的消息类型:" + message.getType()); + } + // 通用字段 + builder.fromUser(account.getAccount()); + builder.toUser(message.getOpenid()); + return builder.build(); + } + List convertList02(List list); + + default WxMpKefuMessage convert(MpMessageSendReqVO sendReqVO, MpUserDO user) { + me.chanjar.weixin.mp.builder.kefu.BaseBuilder builder; + // 个性化字段 + switch (sendReqVO.getType()) { + case WxConsts.KefuMsgType.TEXT: + builder = WxMpKefuMessage.TEXT().content(sendReqVO.getContent()); + break; + case WxConsts.KefuMsgType.IMAGE: + builder = WxMpKefuMessage.IMAGE().mediaId(sendReqVO.getMediaId()); + break; + case WxConsts.KefuMsgType.VOICE: + builder = WxMpKefuMessage.VOICE().mediaId(sendReqVO.getMediaId()); + break; + case WxConsts.KefuMsgType.VIDEO: + builder = WxMpKefuMessage.VIDEO().mediaId(sendReqVO.getMediaId()) + .title(sendReqVO.getTitle()).description(sendReqVO.getDescription()); + break; + case WxConsts.KefuMsgType.NEWS: + builder = WxMpKefuMessage.NEWS().articles(convertList03(sendReqVO.getArticles())); + break; + case WxConsts.KefuMsgType.MUSIC: + builder = WxMpKefuMessage.MUSIC().title(sendReqVO.getTitle()).description(sendReqVO.getDescription()) + .thumbMediaId(sendReqVO.getThumbMediaId()) + .musicUrl(sendReqVO.getMusicUrl()).hqMusicUrl(sendReqVO.getHqMusicUrl()); + break; + default: + throw new IllegalArgumentException("不支持的消息类型:" + sendReqVO.getType()); + } + // 通用字段 + builder.toUser(user.getOpenid()); + return builder.build(); + } + List convertList03(List list); + + default MpMessageDO convert(WxMpKefuMessage wxMessage, MpAccountDO account, MpUserDO user) { + MpMessageDO message = convert(wxMessage); + if (account != null) { + message.setAccountId(account.getId()).setAppId(account.getAppId()); + } + if (user != null) { + message.setUserId(user.getId()).setOpenid(user.getOpenid()); + } + return message; + } + @Mappings(value = { + @Mapping(source = "msgType", target = "type"), + @Mapping(target = "createTime", ignore = true), + }) + MpMessageDO convert(WxMpKefuMessage bean); + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/convert/statistics/MpStatisticsConvert.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/convert/statistics/MpStatisticsConvert.java new file mode 100644 index 0000000..9d4d7e3 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/convert/statistics/MpStatisticsConvert.java @@ -0,0 +1,52 @@ +package cn.iocoder.yudao.module.mp.convert.statistics; + +import cn.iocoder.yudao.module.mp.controller.admin.statistics.vo.MpStatisticsInterfaceSummaryRespVO; +import cn.iocoder.yudao.module.mp.controller.admin.statistics.vo.MpStatisticsUpstreamMessageRespVO; +import cn.iocoder.yudao.module.mp.controller.admin.statistics.vo.MpStatisticsUserCumulateRespVO; +import cn.iocoder.yudao.module.mp.controller.admin.statistics.vo.MpStatisticsUserSummaryRespVO; +import me.chanjar.weixin.mp.bean.datacube.WxDataCubeInterfaceResult; +import me.chanjar.weixin.mp.bean.datacube.WxDataCubeMsgResult; +import me.chanjar.weixin.mp.bean.datacube.WxDataCubeUserCumulate; +import me.chanjar.weixin.mp.bean.datacube.WxDataCubeUserSummary; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.Named; +import org.mapstruct.factory.Mappers; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY; + +@Mapper +public interface MpStatisticsConvert { + + MpStatisticsConvert INSTANCE = Mappers.getMapper(MpStatisticsConvert.class); + + List convertList01(List list); + + List convertList02(List list); + + List convertList03(List list); + + @Mappings({ + @Mapping(target = "refDate", expression = "java(dateFormat0(bean.getRefDate()))"), + @Mapping(source = "msgUser", target = "messageUser"), + @Mapping(source = "msgCount", target = "messageCount"), + }) + MpStatisticsUpstreamMessageRespVO convert(WxDataCubeMsgResult bean); + + List convertList04(List list); + + @Mapping(target = "refDate", expression = "java(dateFormat0(bean.getRefDate()))") + MpStatisticsInterfaceSummaryRespVO convert(WxDataCubeInterfaceResult bean); + + @Named("dateFormat0") + default LocalDateTime dateFormat0(String date) { + return LocalDate.parse(date, DateTimeFormatter.ofPattern(FORMAT_YEAR_MONTH_DAY)).atStartOfDay(); + } + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/convert/tag/MpTagConvert.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/convert/tag/MpTagConvert.java new file mode 100644 index 0000000..727ccd3 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/convert/tag/MpTagConvert.java @@ -0,0 +1,44 @@ +package cn.iocoder.yudao.module.mp.convert.tag; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.mp.controller.admin.tag.vo.MpTagRespVO; +import cn.iocoder.yudao.module.mp.controller.admin.tag.vo.MpTagSimpleRespVO; +import cn.iocoder.yudao.module.mp.controller.admin.tag.vo.MpTagUpdateReqVO; +import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; +import cn.iocoder.yudao.module.mp.dal.dataobject.tag.MpTagDO; +import me.chanjar.weixin.mp.bean.tag.WxUserTag; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.factory.Mappers; + +import java.util.List; + +@Mapper +public interface MpTagConvert { + + MpTagConvert INSTANCE = Mappers.getMapper(MpTagConvert.class); + + WxUserTag convert(MpTagUpdateReqVO bean); + + MpTagRespVO convert(WxUserTag bean); + + List convertList(List list); + + PageResult convertPage(PageResult page); + + @Mappings({ + @Mapping(target = "id", ignore = true), + @Mapping(source = "tag.id", target = "tagId"), + @Mapping(source = "tag.name", target = "name"), + @Mapping(source = "tag.count", target = "count"), + @Mapping(source = "account.id", target = "accountId"), + @Mapping(source = "account.appId", target = "appId"), + }) + MpTagDO convert(WxUserTag tag, MpAccountDO account); + + MpTagRespVO convert(MpTagDO mpTagDO); + + List convertList02(List list); + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/convert/user/MpUserConvert.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/convert/user/MpUserConvert.java new file mode 100644 index 0000000..13ed0b3 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/convert/user/MpUserConvert.java @@ -0,0 +1,56 @@ +package cn.iocoder.yudao.module.mp.convert.user; + +import cn.hutool.core.date.LocalDateTimeUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; +import cn.iocoder.yudao.module.mp.controller.admin.user.vo.MpUserRespVO; +import cn.iocoder.yudao.module.mp.controller.admin.user.vo.MpUserUpdateReqVO; +import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; +import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO; +import me.chanjar.weixin.mp.bean.result.WxMpUser; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.factory.Mappers; + +import java.util.List; + +@Mapper +public interface MpUserConvert { + + MpUserConvert INSTANCE = Mappers.getMapper(MpUserConvert.class); + + MpUserRespVO convert(MpUserDO bean); + + List convertList(List list); + + PageResult convertPage(PageResult page); + + @Mappings(value = { + @Mapping(source = "openId", target = "openid"), + @Mapping(source = "unionId", target = "unionId"), + @Mapping(source = "headImgUrl", target = "headImageUrl"), + @Mapping(target = "subscribeTime", ignore = true), // 单独转换 + }) + MpUserDO convert(WxMpUser wxMpUser); + + default MpUserDO convert(MpAccountDO account, WxMpUser wxMpUser) { + MpUserDO user = convert(wxMpUser); + user.setSubscribeStatus(wxMpUser.getSubscribe() ? CommonStatusEnum.ENABLE.getStatus() + : CommonStatusEnum.DISABLE.getStatus()); + user.setSubscribeTime(LocalDateTimeUtil.of(wxMpUser.getSubscribeTime() * 1000L)); + if (account != null) { + user.setAccountId(account.getId()); + user.setAppId(account.getAppId()); + } + return user; + } + + default List convertList(MpAccountDO account, List wxUsers) { + return CollectionUtils.convertList(wxUsers, wxUser -> convert(account, wxUser)); + } + + MpUserDO convert(MpUserUpdateReqVO bean); + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/account/MpAccountDO.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/account/MpAccountDO.java new file mode 100644 index 0000000..eddf87b --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/account/MpAccountDO.java @@ -0,0 +1,62 @@ +package cn.iocoder.yudao.module.mp.dal.dataobject.account; + +import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +/** + * 公众号账号 DO + * + * @author 芋道源码 + */ +@TableName("mp_account") +@KeySequence("mp_account_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class MpAccountDO extends TenantBaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 公众号名称 + */ + private String name; + /** + * 公众号账号 + */ + private String account; + /** + * 公众号 appid + */ + private String appId; + /** + * 公众号密钥 + */ + private String appSecret; + /** + * 公众号token + */ + private String token; + /** + * 消息加解密密钥 + */ + private String aesKey; + /** + * 二维码图片 URL + */ + private String qrCodeUrl; + /** + * 备注 + */ + private String remark; + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/material/MpMaterialDO.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/material/MpMaterialDO.java new file mode 100644 index 0000000..735235e --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/material/MpMaterialDO.java @@ -0,0 +1,99 @@ +package cn.iocoder.yudao.module.mp.dal.dataobject.material; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; +import me.chanjar.weixin.common.api.WxConsts; + +/** + * 公众号素材 DO + * + * 1. 临时素材 + * 2. 永久素材 + * + * @author 芋道源码 + */ +@TableName("mp_material") +@KeySequence("mp_material_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class MpMaterialDO extends BaseDO { + + /** + * 主键 + */ + @TableId + private Long id; + /** + * 公众号账号的编号 + * + * 关联 {@link MpAccountDO#getId()} + */ + private Long accountId; + /** + * 公众号 appId + * + * 冗余 {@link MpAccountDO#getAppId()} + */ + private String appId; + + /** + * 公众号素材 id + */ + private String mediaId; + /** + * 文件类型 + * + * 枚举 {@link WxConsts.MediaFileType} + */ + private String type; + /** + * 是否永久 + * + * true - 永久素材 + * false - 临时素材 + */ + private Boolean permanent; + /** + * 文件服务器的 URL + */ + private String url; + + /** + * 名字 + * + * 永久素材:非空 + * 临时素材:可能为空。 + * 1. 为空的情况:粉丝主动发送的图片、语音等 + * 2. 非空的情况:主动发送给粉丝的图片、语音等 + */ + private String name; + + /** + * 公众号文件 URL + * + * 只有【永久素材】使用 + */ + private String mpUrl; + + /** + * 视频素材的标题 + * + * 只有【永久素材】使用 + */ + private String title; + /** + * 视频素材的描述 + * + * 只有【永久素材】使用 + */ + private String introduction; + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/menu/MpMenuDO.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/menu/MpMenuDO.java new file mode 100644 index 0000000..6ae8e6b --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/menu/MpMenuDO.java @@ -0,0 +1,185 @@ +package cn.iocoder.yudao.module.mp.dal.dataobject.menu; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; +import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.common.api.WxConsts.MenuButtonType; + +import java.util.List; + +/** + * 公众号菜单 DO + * + * @author 芋道源码 + */ +@TableName(value = "mp_menu", autoResultMap = true) +@KeySequence("mp_menu_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class MpMenuDO extends BaseDO { + + /** + * 编号 - 顶级菜单 + */ + public static final Long ID_ROOT = 0L; + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 公众号账号的编号 + * + * 关联 {@link MpAccountDO#getId()} + */ + private Long accountId; + /** + * 公众号 appId + * + * 冗余 {@link MpAccountDO#getAppId()} + */ + private String appId; + + /** + * 菜单名称 + */ + private String name; + /** + * 菜单标识 + * + * 支持多 DB 类型时,无法直接使用 key + @TableField("menuKey") 来实现转换,原因是 "menuKey" AS key 而存在报错 + */ + private String menuKey; + /** + * 父菜单编号 + */ + private Long parentId; + + // ========== 按钮操作 ========== + + /** + * 按钮类型 + * + * 枚举 {@link MenuButtonType} + */ + private String type; + + /** + * 网页链接 + * + * 粉丝点击菜单可打开链接,不超过 1024 字节 + * + * 类型为 {@link WxConsts.XmlMsgType} 的 VIEW、MINIPROGRAM + */ + private String url; + + /** + * 小程序的 appId + * + * 类型为 {@link MenuButtonType} 的 MINIPROGRAM + */ + private String miniProgramAppId; + /** + * 小程序的页面路径 + * + * 类型为 {@link MenuButtonType} 的 MINIPROGRAM + */ + private String miniProgramPagePath; + + /** + * 跳转图文的媒体编号 + */ + private String articleId; + + // ========== 消息内容 ========== + + /** + * 消息类型 + * + * 当 {@link #type} 为 CLICK、SCANCODE_WAITMSG + * + * 枚举 {@link WxConsts.XmlMsgType} 中的 TEXT、IMAGE、VOICE、VIDEO、NEWS、MUSIC + */ + private String replyMessageType; + + /** + * 回复的消息内容 + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 TEXT + */ + private String replyContent; + + /** + * 回复的媒体 id + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 IMAGE、VOICE、VIDEO + */ + private String replyMediaId; + /** + * 回复的媒体 URL + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 IMAGE、VOICE、VIDEO + */ + private String replyMediaUrl; + + /** + * 回复的标题 + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO + */ + private String replyTitle; + /** + * 回复的描述 + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO + */ + private String replyDescription; + + /** + * 回复的缩略图的媒体 id,通过素材管理中的接口上传多媒体文件,得到的 id + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC、VIDEO + */ + private String replyThumbMediaId; + /** + * 回复的缩略图的媒体 URL + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC、VIDEO + */ + private String replyThumbMediaUrl; + + /** + * 回复的图文消息数组 + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private List replyArticles; + + /** + * 回复的音乐链接 + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC + */ + private String replyMusicUrl; + /** + * 回复的高质量音乐链接 + * + * WIFI 环境优先使用该链接播放音乐 + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC + */ + private String replyHqMusicUrl; + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/message/MpAutoReplyDO.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/message/MpAutoReplyDO.java new file mode 100644 index 0000000..6804e40 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/message/MpAutoReplyDO.java @@ -0,0 +1,165 @@ +package cn.iocoder.yudao.module.mp.dal.dataobject.message; + +import cn.iocoder.yudao.framework.common.util.collection.SetUtils; +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; +import cn.iocoder.yudao.module.mp.enums.message.MpAutoReplyMatchEnum; +import cn.iocoder.yudao.module.mp.enums.message.MpAutoReplyTypeEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.common.api.WxConsts.XmlMsgType; + +import java.util.List; +import java.util.Set; + +/** + * 公众号消息自动回复 DO + * + * @author 芋道源码 + */ +@TableName(value = "mp_auto_reply", autoResultMap = true) +@KeySequence("mp_auto_reply_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class MpAutoReplyDO extends BaseDO { + + public static Set REQUEST_MESSAGE_TYPE = SetUtils.asSet(WxConsts.XmlMsgType.TEXT, WxConsts.XmlMsgType.IMAGE, + WxConsts.XmlMsgType.VOICE, WxConsts.XmlMsgType.VIDEO, WxConsts.XmlMsgType.SHORTVIDEO, + WxConsts.XmlMsgType.LOCATION, WxConsts.XmlMsgType.LINK); + + /** + * 主键 + */ + @TableId + private Long id; + /** + * 公众号账号的编号 + * + * 关联 {@link MpAccountDO#getId()} + */ + private Long accountId; + /** + * 公众号 appId + * + * 冗余 {@link MpAccountDO#getAppId()} + */ + private String appId; + + /** + * 回复类型 + * + * 枚举 {@link MpAutoReplyTypeEnum} + */ + private Integer type; + + // ==================== 请求消息 ==================== + + /** + * 请求的关键字 + * + * 当 {@link #type} 为 {@link MpAutoReplyTypeEnum#KEYWORD} + */ + private String requestKeyword; + /** + * 请求的关键字的匹配 + * + * 当 {@link #type} 为 {@link MpAutoReplyTypeEnum#KEYWORD} + * + * 枚举 {@link MpAutoReplyMatchEnum} + */ + private Integer requestMatch; + + /** + * 请求的消息类型 + * + * 当 {@link #type} 为 {@link MpAutoReplyTypeEnum#MESSAGE} + * + * 枚举 {@link XmlMsgType} 中的 {@link #REQUEST_MESSAGE_TYPE} + */ + private String requestMessageType; + + // ==================== 响应消息 ==================== + + /** + * 回复的消息类型 + * + * 枚举 {@link XmlMsgType} 中的 TEXT、IMAGE、VOICE、VIDEO、NEWS + */ + private String responseMessageType; + + /** + * 回复的消息内容 + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 TEXT + */ + private String responseContent; + + /** + * 回复的媒体 id + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 IMAGE、VOICE、VIDEO + */ + private String responseMediaId; + /** + * 回复的媒体 URL + */ + private String responseMediaUrl; + + /** + * 回复的标题 + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO + */ + private String responseTitle; + /** + * 回复的描述 + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO + */ + private String responseDescription; + + /** + * 回复的缩略图的媒体 id,通过素材管理中的接口上传多媒体文件,得到的 id + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC、VIDEO + */ + private String responseThumbMediaId; + /** + * 回复的缩略图的媒体 URL + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC、VIDEO + */ + private String responseThumbMediaUrl; + + /** + * 回复的图文消息 + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private List responseArticles; + + /** + * 回复的音乐链接 + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC + */ + private String responseMusicUrl; + /** + * 回复的高质量音乐链接 + * + * WIFI 环境优先使用该链接播放音乐 + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC + */ + private String responseHqMusicUrl; + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/message/MpMessageDO.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/message/MpMessageDO.java new file mode 100644 index 0000000..f2ddeca --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/message/MpMessageDO.java @@ -0,0 +1,242 @@ +package cn.iocoder.yudao.module.mp.dal.dataobject.message; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; +import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO; +import cn.iocoder.yudao.module.mp.enums.message.MpMessageSendFromEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.mp.builder.kefu.NewsBuilder; + +import java.io.Serializable; +import java.util.List; + +/** + * 公众号消息 DO + * + * @author 芋道源码 + */ +@TableName(value = "mp_message", autoResultMap = true) +@KeySequence("mp_message_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class MpMessageDO extends BaseDO { + + /** + * 主键 + */ + @TableId + private Long id; + /** + * 微信公众号消息 id + */ + private Long msgId; + /** + * 公众号账号的 ID + * + * 关联 {@link MpAccountDO#getId()} + */ + private Long accountId; + /** + * 公众号 appid + * + * 冗余 {@link MpAccountDO#getAppId()} + */ + private String appId; + /** + * 公众号粉丝的编号 + * + * 关联 {@link MpUserDO#getId()} + */ + private Long userId; + /** + * 公众号粉丝标志 + * + * 冗余 {@link MpUserDO#getOpenid()} + */ + private String openid; + + /** + * 消息类型 + * + * 枚举 {@link WxConsts.XmlMsgType} + */ + private String type; + /** + * 消息来源 + * + * 枚举 {@link MpMessageSendFromEnum} + */ + private Integer sendFrom; + + // ========= 普通消息内容 https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_standard_messages.html + + /** + * 消息内容 + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 TEXT + */ + private String content; + + /** + * 媒体文件的编号 + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 IMAGE、VOICE、VIDEO + */ + private String mediaId; + /** + * 媒体文件的 URL + */ + private String mediaUrl; + /** + * 语音识别后文本 + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 VOICE + */ + private String recognition; + /** + * 语音格式,如 amr,speex 等 + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 VOICE + */ + private String format; + /** + * 标题 + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO、MUSIC、LINK + */ + private String title; + /** + * 描述 + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO、MUSIC + */ + private String description; + + /** + * 缩略图的媒体 id,通过素材管理中的接口上传多媒体文件,得到的 id + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC、VIDEO + */ + private String thumbMediaId; + /** + * 缩略图的媒体 URL + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC、VIDEO + */ + private String thumbMediaUrl; + + /** + * 点击图文消息跳转链接 + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 LINK + */ + private String url; + + /** + * 地理位置维度 + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 LOCATION + */ + private Double locationX; + /** + * 地理位置经度 + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 LOCATION + */ + private Double locationY; + /** + * 地图缩放大小 + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 LOCATION + */ + private Double scale; + /** + * 详细地址 + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 LOCATION + * + * 例如说杨浦区黄兴路 221-4 号临 + */ + private String label; + + /** + * 图文消息数组 + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private List
articles; + + /** + * 音乐链接 + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC + */ + private String musicUrl; + /** + * 高质量音乐链接 + * + * WIFI 环境优先使用该链接播放音乐 + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC + */ + private String hqMusicUrl; + + // ========= 事件推送 https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_event_pushes.html + + /** + * 事件类型 + * + * 枚举 {@link WxConsts.EventType} + */ + private String event; + /** + * 事件 Key + * + * 1. {@link WxConsts.EventType} 的 SCAN:qrscene_ 为前缀,后面为二维码的参数值 + * 2. {@link WxConsts.EventType} 的 CLICK:与自定义菜单接口中 KEY 值对应 + */ + private String eventKey; + + /** + * 文章 + */ + @Data + public static class Article implements Serializable { + + /** + * 图文消息标题 + */ + @NotEmpty(message = "图文消息标题不能为空", groups = NewsBuilder.class) + private String title; + /** + * 图文消息描述 + */ + @NotEmpty(message = "图文消息描述不能为空", groups = NewsBuilder.class) + private String description; + /** + * 图片链接 + * + * 支持 JPG、PNG 格式,较好的效果为大图 360*200,小图 200*200 + */ + @NotEmpty(message = "图片链接不能为空", groups = NewsBuilder.class) + private String picUrl; + /** + * 点击图文消息跳转链接 + */ + @NotEmpty(message = "点击图文消息跳转链接不能为空", groups = NewsBuilder.class) + private String url; + + } + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/tag/MpTagDO.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/tag/MpTagDO.java new file mode 100644 index 0000000..8a5b9f6 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/tag/MpTagDO.java @@ -0,0 +1,58 @@ +package cn.iocoder.yudao.module.mp.dal.dataobject.tag; + +import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; +import lombok.*; + +import com.baomidou.mybatisplus.annotation.*; +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import me.chanjar.weixin.mp.bean.tag.WxUserTag; + +/** + * 公众号标签 DO + * + * @author 芋道源码 + */ +@TableName("mp_tag") +@KeySequence("mp_tag_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class MpTagDO extends BaseDO { + + /** + * 主键 + */ + @TableId(type = IdType.INPUT) + private Long id; + /** + * 公众号标签 id + */ + private Long tagId; + /** + * 标签名 + */ + private String name; + /** + * 此标签下粉丝数 + * + * 冗余:{@link WxUserTag#getCount()} 字段,需要管理员点击【同步】后,更新该字段 + */ + private Integer count; + + /** + * 公众号账号的编号 + * + * 关联 {@link MpAccountDO#getId()} + */ + private Long accountId; + /** + * 公众号 appId + * + * 冗余 {@link MpAccountDO#getAppId()} + */ + private String appId; + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/user/MpUserDO.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/user/MpUserDO.java new file mode 100644 index 0000000..418e96f --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/user/MpUserDO.java @@ -0,0 +1,114 @@ +package cn.iocoder.yudao.module.mp.dal.dataobject.user; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler; +import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; +import cn.iocoder.yudao.module.mp.dal.dataobject.tag.MpTagDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 微信公众号粉丝 DO + * + * @author 芋道源码 + */ +@TableName(value = "mp_user", autoResultMap = true) +@KeySequence("mp_user_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class MpUserDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 粉丝标识 + */ + private String openid; + /** + * 微信生态唯一标识 + */ + private String unionId; + /** + * 关注状态 + * + * 枚举 {@link CommonStatusEnum} + * 1. 开启 - 已关注 + * 2. 禁用 - 取消关注 + */ + private Integer subscribeStatus; + /** + * 关注时间 + */ + private LocalDateTime subscribeTime; + /** + * 取消关注时间 + */ + private LocalDateTime unsubscribeTime; + /** + * 昵称 + * + * 注意,2021-12-27 公众号接口不再返回头像和昵称,只能通过微信公众号的网页登录获取 + */ + private String nickname; + /** + * 头像地址 + * + * 注意,2021-12-27 公众号接口不再返回头像和昵称,只能通过微信公众号的网页登录获取 + */ + private String headImageUrl; + /** + * 语言 + */ + private String language; + /** + * 国家 + */ + private String country; + /** + * 省份 + */ + private String province; + /** + * 城市 + */ + private String city; + /** + * 备注 + */ + private String remark; + /** + * 标签编号数组 + * + * 注意,对应的是 {@link MpTagDO#getTagId()} 字段 + */ + @TableField(typeHandler = LongListTypeHandler.class) + private List tagIds; + + /** + * 公众号账号的编号 + * + * 关联 {@link MpAccountDO#getId()} + */ + private Long accountId; + /** + * 公众号 appId + * + * 冗余 {@link MpAccountDO#getAppId()} + */ + private String appId; + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/account/MpAccountMapper.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/account/MpAccountMapper.java new file mode 100644 index 0000000..30c54fd --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/account/MpAccountMapper.java @@ -0,0 +1,31 @@ +package cn.iocoder.yudao.module.mp.dal.mysql.account; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountPageReqVO; +import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Select; + +import java.time.LocalDateTime; + +@Mapper +public interface MpAccountMapper extends BaseMapperX { + + default PageResult selectPage(MpAccountPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(MpAccountDO::getName, reqVO.getName()) + .likeIfPresent(MpAccountDO::getAccount, reqVO.getAccount()) + .likeIfPresent(MpAccountDO::getAppId, reqVO.getAppId()) + .orderByDesc(MpAccountDO::getId)); + } + + default MpAccountDO selectByAppId(String appId) { + return selectOne(MpAccountDO::getAppId, appId); + } + + @Select("SELECT COUNT(*) FROM mp_account WHERE update_time > #{maxUpdateTime}") + Long selectCountByUpdateTimeGt(LocalDateTime maxUpdateTime); + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/material/MpMaterialMapper.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/material/MpMaterialMapper.java new file mode 100644 index 0000000..ca3c6b3 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/material/MpMaterialMapper.java @@ -0,0 +1,33 @@ +package cn.iocoder.yudao.module.mp.dal.mysql.material; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.mp.controller.admin.material.vo.MpMaterialPageReqVO; +import cn.iocoder.yudao.module.mp.dal.dataobject.material.MpMaterialDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Collection; +import java.util.List; + +@Mapper +public interface MpMaterialMapper extends BaseMapperX { + + default MpMaterialDO selectByAccountIdAndMediaId(Long accountId, String mediaId) { + return selectOne(MpMaterialDO::getAccountId, accountId, + MpMaterialDO::getMediaId, mediaId); + } + + default PageResult selectPage(MpMaterialPageReqVO pageReqVO) { + return selectPage(pageReqVO, new LambdaQueryWrapperX() + .eq(MpMaterialDO::getAccountId, pageReqVO.getAccountId()) + .eqIfPresent(MpMaterialDO::getPermanent, pageReqVO.getPermanent()) + .eqIfPresent(MpMaterialDO::getType, pageReqVO.getType()) + .orderByDesc(MpMaterialDO::getId)); + } + + default List selectListByMediaId(Collection mediaIds) { + return selectList(MpMaterialDO::getMediaId, mediaIds); + } + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/menu/MpMenuMapper.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/menu/MpMenuMapper.java new file mode 100644 index 0000000..fccd7f2 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/menu/MpMenuMapper.java @@ -0,0 +1,25 @@ +package cn.iocoder.yudao.module.mp.dal.mysql.menu; + +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.mp.dal.dataobject.menu.MpMenuDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface MpMenuMapper extends BaseMapperX { + + default MpMenuDO selectByAppIdAndMenuKey(String appId, String menuKey) { + return selectOne(MpMenuDO::getAppId, appId, + MpMenuDO::getMenuKey, menuKey); + } + + default List selectListByAccountId(Long accountId) { + return selectList(MpMenuDO::getAccountId, accountId); + } + + default void deleteByAccountId(Long accountId) { + delete(new LambdaQueryWrapperX().eq(MpMenuDO::getAccountId, accountId)); + } +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/message/MpAutoReplyMapper.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/message/MpAutoReplyMapper.java new file mode 100644 index 0000000..c8260e8 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/message/MpAutoReplyMapper.java @@ -0,0 +1,70 @@ +package cn.iocoder.yudao.module.mp.dal.mysql.message; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.mp.controller.admin.message.vo.message.MpMessagePageReqVO; +import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpAutoReplyDO; +import cn.iocoder.yudao.module.mp.enums.message.MpAutoReplyMatchEnum; +import cn.iocoder.yudao.module.mp.enums.message.MpAutoReplyTypeEnum; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface MpAutoReplyMapper extends BaseMapperX { + + default PageResult selectPage(MpMessagePageReqVO pageVO) { + return selectPage(pageVO, new LambdaQueryWrapperX() + .eq(MpAutoReplyDO::getAccountId, pageVO.getAccountId()) + .eqIfPresent(MpAutoReplyDO::getType, pageVO.getType())); + } + + default List selectListByAppIdAndKeywordAll(String appId, String requestKeyword) { + return selectList(new LambdaQueryWrapperX() + .eq(MpAutoReplyDO::getAppId, appId) + .eq(MpAutoReplyDO::getType, MpAutoReplyTypeEnum.KEYWORD.getType()) + .eq(MpAutoReplyDO::getRequestMatch, MpAutoReplyMatchEnum.ALL.getMatch()) + .eq(MpAutoReplyDO::getRequestKeyword, requestKeyword)); + } + + default List selectListByAppIdAndKeywordLike(String appId, String requestKeyword) { + return selectList(new LambdaQueryWrapperX() + .eq(MpAutoReplyDO::getAppId, appId) + .eq(MpAutoReplyDO::getType, MpAutoReplyTypeEnum.KEYWORD.getType()) + .eq(MpAutoReplyDO::getRequestMatch, MpAutoReplyMatchEnum.LIKE.getMatch()) + .like(MpAutoReplyDO::getRequestKeyword, requestKeyword)); + } + + default List selectListByAppIdAndMessage(String appId, String requestMessageType) { + return selectList(new LambdaQueryWrapperX() + .eq(MpAutoReplyDO::getAppId, appId) + .eq(MpAutoReplyDO::getType, MpAutoReplyTypeEnum.MESSAGE.getType()) + .eq(MpAutoReplyDO::getRequestMessageType, requestMessageType)); + } + + default List selectListByAppIdAndSubscribe(String appId) { + return selectList(new LambdaQueryWrapperX() + .eq(MpAutoReplyDO::getAppId, appId) + .eq(MpAutoReplyDO::getType, MpAutoReplyTypeEnum.SUBSCRIBE.getType())); + } + + default MpAutoReplyDO selectByAccountIdAndSubscribe(Long accountId) { + return selectOne(MpAutoReplyDO::getAccountId, accountId, + MpAutoReplyDO::getType, MpAutoReplyTypeEnum.SUBSCRIBE.getType()); + } + + default MpAutoReplyDO selectByAccountIdAndMessage(Long accountId, String requestMessageType) { + return selectOne(new LambdaQueryWrapperX() + .eq(MpAutoReplyDO::getAccountId, accountId) + .eq(MpAutoReplyDO::getType, MpAutoReplyTypeEnum.MESSAGE.getType()) + .eq(MpAutoReplyDO::getRequestMessageType, requestMessageType)); + } + + default MpAutoReplyDO selectByAccountIdAndKeyword(Long accountId, String requestKeyword) { + return selectOne(new LambdaQueryWrapperX() + .eq(MpAutoReplyDO::getAccountId, accountId) + .eq(MpAutoReplyDO::getType, MpAutoReplyTypeEnum.KEYWORD.getType()) + .eq(MpAutoReplyDO::getRequestKeyword, requestKeyword)); + } +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/message/MpMessageMapper.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/message/MpMessageMapper.java new file mode 100644 index 0000000..72ba566 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/message/MpMessageMapper.java @@ -0,0 +1,22 @@ +package cn.iocoder.yudao.module.mp.dal.mysql.message; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.mp.controller.admin.message.vo.message.MpMessagePageReqVO; +import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface MpMessageMapper extends BaseMapperX { + + default PageResult selectPage(MpMessagePageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(MpMessageDO::getAccountId, reqVO.getAccountId()) + .eqIfPresent(MpMessageDO::getType, reqVO.getType()) + .eqIfPresent(MpMessageDO::getOpenid, reqVO.getOpenid()) + .betweenIfPresent(MpMessageDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(MpMessageDO::getId)); + } + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/tag/MpTagMapper.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/tag/MpTagMapper.java new file mode 100644 index 0000000..760f53a --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/tag/MpTagMapper.java @@ -0,0 +1,26 @@ +package cn.iocoder.yudao.module.mp.dal.mysql.tag; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.mp.controller.admin.tag.vo.MpTagPageReqVO; +import cn.iocoder.yudao.module.mp.dal.dataobject.tag.MpTagDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface MpTagMapper extends BaseMapperX { + + default PageResult selectPage(MpTagPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(MpTagDO::getAccountId, reqVO.getAccountId()) + .likeIfPresent(MpTagDO::getName, reqVO.getName()) + .orderByDesc(MpTagDO::getId)); + } + + default List selectListByAccountId(Long accountId) { + return selectList(MpTagDO::getAccountId, accountId); + } + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/user/MpUserMapper.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/user/MpUserMapper.java new file mode 100644 index 0000000..472b357 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/user/MpUserMapper.java @@ -0,0 +1,35 @@ +package cn.iocoder.yudao.module.mp.dal.mysql.user; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.mp.controller.admin.user.vo.MpUserPageReqVO; +import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface MpUserMapper extends BaseMapperX { + + default PageResult selectPage(MpUserPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(MpUserDO::getOpenid, reqVO.getOpenid()) + .likeIfPresent(MpUserDO::getNickname, reqVO.getNickname()) + .eqIfPresent(MpUserDO::getAccountId, reqVO.getAccountId()) + .orderByDesc(MpUserDO::getId)); + } + + default MpUserDO selectByAppIdAndOpenid(String appId, String openid) { + return selectFirstOne(MpUserDO::getAppId, appId, + MpUserDO::getOpenid, openid); + } + + default List selectListByAppIdAndOpenid(String appId, List openids) { + return selectList(new LambdaQueryWrapperX() + .eq(MpUserDO::getAppId, appId) + .in(MpUserDO::getOpenid, openids)); + + } + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/enums/ErrorCodeConstants.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/enums/ErrorCodeConstants.java new file mode 100644 index 0000000..34c94fd --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/enums/ErrorCodeConstants.java @@ -0,0 +1,64 @@ +package cn.iocoder.yudao.module.mp.enums; + +import cn.iocoder.yudao.framework.common.exception.ErrorCode; + +/** + * Mp 错误码枚举类 + * + * mp 系统,使用 1-006-000-000 段 + */ +public interface ErrorCodeConstants { + + // ========== 公众号账号 1-006-000-000 ============ + ErrorCode ACCOUNT_NOT_EXISTS = new ErrorCode(1_006_000_000, "公众号账号不存在"); + ErrorCode ACCOUNT_GENERATE_QR_CODE_FAIL = new ErrorCode(1_006_000_001, "生成公众号二维码失败,原因:{}"); + ErrorCode ACCOUNT_CLEAR_QUOTA_FAIL = new ErrorCode(1_006_000_002, "清空公众号的 API 配额失败,原因:{}"); + + // ========== 公众号统计 1-006-001-000 ============ + ErrorCode STATISTICS_GET_USER_SUMMARY_FAIL = new ErrorCode(1_006_001_000, "获取粉丝增减数据失败,原因:{}"); + ErrorCode STATISTICS_GET_USER_CUMULATE_FAIL = new ErrorCode(1_006_001_001, "获得粉丝累计数据失败,原因:{}"); + ErrorCode STATISTICS_GET_UPSTREAM_MESSAGE_FAIL = new ErrorCode(1_006_001_002, "获得消息发送概况数据失败,原因:{}"); + ErrorCode STATISTICS_GET_INTERFACE_SUMMARY_FAIL = new ErrorCode(1_006_001_003, "获得接口分析数据失败,原因:{}"); + + // ========== 公众号标签 1-006-002-000 ============ + ErrorCode TAG_NOT_EXISTS = new ErrorCode(1_006_002_000, "标签不存在"); + ErrorCode TAG_CREATE_FAIL = new ErrorCode(1_006_002_001, "创建标签失败,原因:{}"); + ErrorCode TAG_UPDATE_FAIL = new ErrorCode(1_006_002_002, "更新标签失败,原因:{}"); + ErrorCode TAG_DELETE_FAIL = new ErrorCode(1_006_002_003, "删除标签失败,原因:{}"); + ErrorCode TAG_GET_FAIL = new ErrorCode(1_006_002_004, "获得标签失败,原因:{}"); + + // ========== 公众号粉丝 1-006-003-000 ============ + ErrorCode USER_NOT_EXISTS = new ErrorCode(1_006_003_000, "粉丝不存在"); + ErrorCode USER_UPDATE_TAG_FAIL = new ErrorCode(1_006_003_001, "更新粉丝标签失败,原因:{}"); + + // ========== 公众号素材 1-006-004-000 ============ + ErrorCode MATERIAL_NOT_EXISTS = new ErrorCode(1_006_004_000, "素材不存在"); + ErrorCode MATERIAL_UPLOAD_FAIL = new ErrorCode(1_006_004_001, "上传素材失败,原因:{}"); + ErrorCode MATERIAL_IMAGE_UPLOAD_FAIL = new ErrorCode(1_006_004_002, "上传图片失败,原因:{}"); + ErrorCode MATERIAL_DELETE_FAIL = new ErrorCode(1_006_004_003, "删除素材失败,原因:{}"); + + // ========== 公众号消息 1-006-005-000 ============ + ErrorCode MESSAGE_SEND_FAIL = new ErrorCode(1_006_005_000, "发送消息失败,原因:{}"); + + // ========== 公众号发布能力 1-006-006-000 ============ + ErrorCode FREE_PUBLISH_LIST_FAIL = new ErrorCode(1_006_006_000, "获得已成功发布列表失败,原因:{}"); + ErrorCode FREE_PUBLISH_SUBMIT_FAIL = new ErrorCode(1_006_006_001, "提交发布失败,原因:{}"); + ErrorCode FREE_PUBLISH_DELETE_FAIL = new ErrorCode(1_006_006_002, "删除发布失败,原因:{}"); + + // ========== 公众号草稿 1-006-007-000 ============ + ErrorCode DRAFT_LIST_FAIL = new ErrorCode(1_006_007_000, "获得草稿列表失败,原因:{}"); + ErrorCode DRAFT_CREATE_FAIL = new ErrorCode(1_006_007_001, "创建草稿失败,原因:{}"); + ErrorCode DRAFT_UPDATE_FAIL = new ErrorCode(1_006_007_002, "更新草稿失败,原因:{}"); + ErrorCode DRAFT_DELETE_FAIL = new ErrorCode(1_006_007_003, "删除草稿失败,原因:{}"); + + // ========== 公众号菜单 1-006-008-000 ============ + ErrorCode MENU_SAVE_FAIL = new ErrorCode(1_006_008_000, "创建菜单失败,原因:{}"); + ErrorCode MENU_DELETE_FAIL = new ErrorCode(1_006_008_001, "删除菜单失败,原因:{}"); + + // ========== 公众号自动回复 1-006-009-000 ============ + ErrorCode AUTO_REPLY_NOT_EXISTS = new ErrorCode(1_006_009_000, "自动回复不存在"); + ErrorCode AUTO_REPLY_ADD_SUBSCRIBE_FAIL_EXISTS = new ErrorCode(1_006_009_001, "操作失败,原因:已存在关注时的回复"); + ErrorCode AUTO_REPLY_ADD_MESSAGE_FAIL_EXISTS = new ErrorCode(1_006_009_002, "操作失败,原因:已存在该消息类型的回复"); + ErrorCode AUTO_REPLY_ADD_KEYWORD_FAIL_EXISTS = new ErrorCode(1_006_009_003, "操作失败,原因:已关在该关键字的回复"); + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpAutoReplyMatchEnum.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpAutoReplyMatchEnum.java new file mode 100644 index 0000000..a5298db --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpAutoReplyMatchEnum.java @@ -0,0 +1,28 @@ +package cn.iocoder.yudao.module.mp.enums.message; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 公众号消息自动回复的匹配模式 + * + * @author 芋道源码 + */ +@Getter +@AllArgsConstructor +public enum MpAutoReplyMatchEnum { + + ALL(1, "完全匹配"), + LIKE(2, "半匹配"), + ; + + /** + * 匹配 + */ + private final Integer match; + /** + * 匹配的名字 + */ + private final String name; + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpAutoReplyTypeEnum.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpAutoReplyTypeEnum.java new file mode 100644 index 0000000..300f1f2 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpAutoReplyTypeEnum.java @@ -0,0 +1,29 @@ +package cn.iocoder.yudao.module.mp.enums.message; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 公众号消息自动回复的类型 + * + * @author 芋道源码 + */ +@Getter +@AllArgsConstructor +public enum MpAutoReplyTypeEnum { + + SUBSCRIBE(1, "关注时回复"), + MESSAGE(2, "收到消息回复"), + KEYWORD(3, "关键词回复"), + ; + + /** + * 来源 + */ + private final Integer type; + /** + * 类型的名字 + */ + private final String name; + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpMessageSendFromEnum.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpMessageSendFromEnum.java new file mode 100644 index 0000000..a2af484 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpMessageSendFromEnum.java @@ -0,0 +1,28 @@ +package cn.iocoder.yudao.module.mp.enums.message; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 微信公众号消息的发送来源 + * + * @author 芋道源码 + */ +@Getter +@AllArgsConstructor +public enum MpMessageSendFromEnum { + + USER_TO_MP(1, "粉丝发送给公众号"), + MP_TO_USER(2, "公众号发给粉丝"), + ; + + /** + * 来源 + */ + private final Integer from; + /** + * 来源的名字 + */ + private final String name; + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/config/MpConfiguration.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/config/MpConfiguration.java new file mode 100644 index 0000000..c908660 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/config/MpConfiguration.java @@ -0,0 +1,54 @@ +package cn.iocoder.yudao.module.mp.framework.mp.config; + +import cn.iocoder.yudao.module.mp.framework.mp.core.DefaultMpServiceFactory; +import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory; +import cn.iocoder.yudao.module.mp.service.handler.menu.MenuHandler; +import cn.iocoder.yudao.module.mp.service.handler.message.MessageReceiveHandler; +import cn.iocoder.yudao.module.mp.service.handler.message.MessageAutoReplyHandler; +import cn.iocoder.yudao.module.mp.service.handler.other.KfSessionHandler; +import cn.iocoder.yudao.module.mp.service.handler.other.NullHandler; +import cn.iocoder.yudao.module.mp.service.handler.other.ScanHandler; +import cn.iocoder.yudao.module.mp.service.handler.other.StoreCheckNotifyHandler; +import cn.iocoder.yudao.module.mp.service.handler.user.LocationHandler; +import cn.iocoder.yudao.module.mp.service.handler.user.SubscribeHandler; +import cn.iocoder.yudao.module.mp.service.handler.user.UnsubscribeHandler; +import com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties; +import me.chanjar.weixin.common.redis.RedisTemplateWxRedisOps; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.core.StringRedisTemplate; + +/** + * 微信公众号的配置类 + * + * @author 芋道源码 + */ +@Configuration +public class MpConfiguration { + + @Bean + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + public RedisTemplateWxRedisOps redisTemplateWxRedisOps(StringRedisTemplate stringRedisTemplate) { + return new RedisTemplateWxRedisOps(stringRedisTemplate); + } + + @Bean + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + public MpServiceFactory mpServiceFactory(RedisTemplateWxRedisOps redisTemplateWxRedisOps, + WxMpProperties wxMpProperties, + MessageReceiveHandler messageReceiveHandler, + KfSessionHandler kfSessionHandler, + StoreCheckNotifyHandler storeCheckNotifyHandler, + MenuHandler menuHandler, + NullHandler nullHandler, + SubscribeHandler subscribeHandler, + UnsubscribeHandler unsubscribeHandler, + LocationHandler locationHandler, + ScanHandler scanHandler, + MessageAutoReplyHandler messageAutoReplyHandler) { + return new DefaultMpServiceFactory(redisTemplateWxRedisOps, wxMpProperties, + messageReceiveHandler, kfSessionHandler, storeCheckNotifyHandler, menuHandler, + nullHandler, subscribeHandler, unsubscribeHandler, locationHandler, scanHandler, messageAutoReplyHandler); + } + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/DefaultMpServiceFactory.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/DefaultMpServiceFactory.java new file mode 100644 index 0000000..414f73a --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/DefaultMpServiceFactory.java @@ -0,0 +1,177 @@ +package cn.iocoder.yudao.module.mp.framework.mp.core; + +import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; +import cn.iocoder.yudao.module.mp.service.handler.menu.MenuHandler; +import cn.iocoder.yudao.module.mp.service.handler.message.MessageReceiveHandler; +import cn.iocoder.yudao.module.mp.service.handler.message.MessageAutoReplyHandler; +import cn.iocoder.yudao.module.mp.service.handler.other.KfSessionHandler; +import cn.iocoder.yudao.module.mp.service.handler.other.NullHandler; +import cn.iocoder.yudao.module.mp.service.handler.other.ScanHandler; +import cn.iocoder.yudao.module.mp.service.handler.other.StoreCheckNotifyHandler; +import cn.iocoder.yudao.module.mp.service.handler.user.LocationHandler; +import cn.iocoder.yudao.module.mp.service.handler.user.SubscribeHandler; +import cn.iocoder.yudao.module.mp.service.handler.user.UnsubscribeHandler; +import com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties; +import com.google.common.collect.Maps; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.common.redis.RedisTemplateWxRedisOps; +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.WxMpRedisConfigImpl; +import me.chanjar.weixin.mp.constant.WxMpEventConstants; + +import java.util.List; +import java.util.Map; + +/** + * 默认的 {@link MpServiceFactory} 实现类 + * + * @author 芋道源码 + */ +@Slf4j +@RequiredArgsConstructor +public class DefaultMpServiceFactory implements MpServiceFactory { + + /** + * 微信 appId 与 WxMpService 的映射 + */ + private volatile Map appId2MpServices; + /** + * 公众号账号 id 与 WxMpService 的映射 + */ + private volatile Map id2MpServices; + /** + * 微信 appId 与 WxMpMessageRouter 的映射 + */ + private volatile Map mpMessageRouters; + + private final RedisTemplateWxRedisOps redisTemplateWxRedisOps; + private final WxMpProperties mpProperties; + + // ========== 各种 Handler ========== + + private final MessageReceiveHandler messageReceiveHandler; + private final KfSessionHandler kfSessionHandler; + private final StoreCheckNotifyHandler storeCheckNotifyHandler; + private final MenuHandler menuHandler; + private final NullHandler nullHandler; + private final SubscribeHandler subscribeHandler; + private final UnsubscribeHandler unsubscribeHandler; + private final LocationHandler locationHandler; + private final ScanHandler scanHandler; + private final MessageAutoReplyHandler messageAutoReplyHandler; + + @Override + public void init(List list) { + Map appId2MpServices = Maps.newHashMap(); + Map id2MpServices = Maps.newHashMap(); + Map mpMessageRouters = Maps.newHashMap(); + // 处理 list + list.forEach(account -> { + // 构建 WxMpService 对象 + WxMpService mpService = buildMpService(account); + appId2MpServices.put(account.getAppId(), mpService); + id2MpServices.put(account.getId(), mpService); + // 构建 WxMpMessageRouter 对象 + WxMpMessageRouter mpMessageRouter = buildMpMessageRouter(mpService); + mpMessageRouters.put(account.getAppId(), mpMessageRouter); + }); + + // 设置到缓存 + this.appId2MpServices = appId2MpServices; + this.id2MpServices = id2MpServices; + this.mpMessageRouters = mpMessageRouters; + } + + @Override + public WxMpService getMpService(Long id) { + return id2MpServices.get(id); + } + + @Override + public WxMpService getMpService(String appId) { + return appId2MpServices.get(appId); + } + + @Override + public WxMpMessageRouter getMpMessageRouter(String appId) { + return mpMessageRouters.get(appId); + } + + private WxMpService buildMpService(MpAccountDO account) { + // 第一步,创建 WxMpRedisConfigImpl 对象 + WxMpRedisConfigImpl configStorage = new WxMpRedisConfigImpl( + redisTemplateWxRedisOps, mpProperties.getConfigStorage().getKeyPrefix()); + configStorage.setAppId(account.getAppId()); + configStorage.setSecret(account.getAppSecret()); + configStorage.setToken(account.getToken()); + configStorage.setAesKey(account.getAesKey()); + + // 第二步,创建 WxMpService 对象 + WxMpService service = new WxMpServiceImpl(); + service.setWxMpConfigStorage(configStorage); + return service; + } + + private WxMpMessageRouter buildMpMessageRouter(WxMpService mpService) { + WxMpMessageRouter router = new WxMpMessageRouter(mpService); + // 记录所有事件的日志(异步执行) + router.rule().handler(messageReceiveHandler).next(); + + // 接收客服会话管理事件 + router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) + .event(WxMpEventConstants.CustomerService.KF_CREATE_SESSION) + .handler(kfSessionHandler).end(); + router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) + .event(WxMpEventConstants.CustomerService.KF_CLOSE_SESSION) + .handler(kfSessionHandler) + .end(); + router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) + .event(WxMpEventConstants.CustomerService.KF_SWITCH_SESSION) + .handler(kfSessionHandler).end(); + + // 门店审核事件 + router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) + .event(WxMpEventConstants.POI_CHECK_NOTIFY) + .handler(storeCheckNotifyHandler).end(); + + // 自定义菜单事件 + router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) + .event(WxConsts.MenuButtonType.CLICK).handler(menuHandler).end(); + + // 点击菜单连接事件 + router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) + .event(WxConsts.MenuButtonType.VIEW).handler(nullHandler).end(); + + // 关注事件 + router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) + .event(WxConsts.EventType.SUBSCRIBE).handler(subscribeHandler) + .end(); + + // 取消关注事件 + router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) + .event(WxConsts.EventType.UNSUBSCRIBE) + .handler(unsubscribeHandler).end(); + + // 上报地理位置事件 + router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) + .event(WxConsts.EventType.LOCATION).handler(locationHandler) + .end(); + + // 接收地理位置消息 + router.rule().async(false).msgType(WxConsts.XmlMsgType.LOCATION) + .handler(locationHandler).end(); + + // 扫码事件 + router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) + .event(WxConsts.EventType.SCAN).handler(scanHandler).end(); + + // 默认 + router.rule().async(false).handler(messageAutoReplyHandler).end(); + return router; + } + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/MpServiceFactory.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/MpServiceFactory.java new file mode 100644 index 0000000..ce017ff --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/MpServiceFactory.java @@ -0,0 +1,66 @@ +package cn.iocoder.yudao.module.mp.framework.mp.core; + +import cn.hutool.core.lang.Assert; +import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; +import me.chanjar.weixin.mp.api.WxMpMessageRouter; +import me.chanjar.weixin.mp.api.WxMpService; + +import java.util.List; + +/** + * {@link WxMpService} 工厂接口 + * + * @author 芋道源码 + */ +public interface MpServiceFactory { + + /** + * 基于微信公众号的账号,初始化对应的 WxMpService 与 WxMpMessageRouter 实例 + * + * @param list 公众号的账号列表 + */ + void init(List list); + + /** + * 获得 id 对应的 WxMpService 实例 + * + * @param id 微信公众号的编号 + * @return WxMpService 实例 + */ + WxMpService getMpService(Long id); + + default WxMpService getRequiredMpService(Long id) { + WxMpService wxMpService = getMpService(id); + Assert.notNull(wxMpService, "找到对应 id({}) 的 WxMpService,请核实!", id); + return wxMpService; + } + + /** + * 获得 appId 对应的 WxMpService 实例 + * + * @param appId 微信公众号 appId + * @return WxMpService 实例 + */ + WxMpService getMpService(String appId); + + default WxMpService getRequiredMpService(String appId) { + WxMpService wxMpService = getMpService(appId); + Assert.notNull(wxMpService, "找到对应 appId({}) 的 WxMpService,请核实!", appId); + return wxMpService; + } + + /** + * 获得 appId 对应的 WxMpMessageRouter 实例 + * + * @param appId 微信公众号 appId + * @return WxMpMessageRouter 实例 + */ + WxMpMessageRouter getMpMessageRouter(String appId); + + default WxMpMessageRouter getRequiredMpMessageRouter(String appId) { + WxMpMessageRouter wxMpMessageRouter = getMpMessageRouter(appId); + Assert.notNull(wxMpMessageRouter, "找到对应 appId({}) 的 WxMpMessageRouter,请核实!", appId); + return wxMpMessageRouter; + } + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/context/MpContextHolder.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/context/MpContextHolder.java new file mode 100644 index 0000000..e5c45c5 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/context/MpContextHolder.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2018-2025, lengleng All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * Neither the name of the pig4cloud.com developer nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * Author: lengleng (wangiegie@gmail.com) + */ + +package cn.iocoder.yudao.module.mp.framework.mp.core.context; + +import cn.iocoder.yudao.module.mp.controller.admin.open.vo.MpOpenHandleMessageReqVO; +import com.alibaba.ttl.TransmittableThreadLocal; +import lombok.experimental.UtilityClass; +import me.chanjar.weixin.mp.api.WxMpMessageHandler; + +/** + * 微信上下文 Context + * + * 目的:解决微信多公众号的问题,在 {@link WxMpMessageHandler} 实现类中,可以通过 {@link #getAppId()} 获取到当前的 appId + * + * @see cn.iocoder.yudao.module.mp.controller.admin.open.MpOpenController#handleMessage(String, String, MpOpenHandleMessageReqVO) + * + * @author 芋道源码 + */ +public class MpContextHolder { + + /** + * 微信公众号的 appId 上下文 + */ + private static final ThreadLocal APPID = new TransmittableThreadLocal<>(); + + public static void setAppId(String appId) { + APPID.set(appId); + } + + public static String getAppId() { + return APPID.get(); + } + + public static void clear() { + APPID.remove(); + } + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/util/MpUtils.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/util/MpUtils.java new file mode 100644 index 0000000..1b52f83 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/util/MpUtils.java @@ -0,0 +1,167 @@ +package cn.iocoder.yudao.module.mp.framework.mp.core.util; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.api.WxConsts; + +import jakarta.validation.Validator; + +/** + * 公众号工具类 + * + * @author 芋道源码 + */ +@Slf4j +public class MpUtils { + + /** + * 校验消息的格式是否符合要求 + * + * @param type 类型 + * @param message 消息 + */ + public static void validateMessage(Validator validator, String type, Object message) { + // 获得对应的校验 group + Class group; + switch (type) { + case WxConsts.XmlMsgType.TEXT: + group = TextMessageGroup.class; + break; + case WxConsts.XmlMsgType.IMAGE: + group = ImageMessageGroup.class; + break; + case WxConsts.XmlMsgType.VOICE: + group = VoiceMessageGroup.class; + break; + case WxConsts.XmlMsgType.VIDEO: + group = VideoMessageGroup.class; + break; + case WxConsts.XmlMsgType.NEWS: + group = NewsMessageGroup.class; + break; + case WxConsts.XmlMsgType.MUSIC: + group = MusicMessageGroup.class; + break; + default: + log.error("[validateMessage][未知的消息类型({})]", message); + throw new IllegalArgumentException("不支持的消息类型:" + type); + } + // 执行校验 + ValidationUtils.validate(validator, message, group); + } + + public static void validateButton(Validator validator, String type, String messageType, Object button) { + if (StrUtil.isBlank(type)) { + return; + } + // 获得对应的校验 group + Class group; + switch (type) { + case WxConsts.MenuButtonType.CLICK: + group = ClickButtonGroup.class; + validateMessage(validator, messageType, button); // 需要额外校验回复的消息格式 + break; + case WxConsts.MenuButtonType.VIEW: + group = ViewButtonGroup.class; + break; + case WxConsts.MenuButtonType.MINIPROGRAM: + group = MiniProgramButtonGroup.class; + break; + case WxConsts.MenuButtonType.SCANCODE_WAITMSG: + group = ScanCodeWaitMsgButtonGroup.class; + validateMessage(validator, messageType, button); // 需要额外校验回复的消息格式 + break; + case "article_" + WxConsts.MenuButtonType.VIEW_LIMITED: + group = ViewLimitedButtonGroup.class; + break; + case WxConsts.MenuButtonType.SCANCODE_PUSH: // 不用校验,直接 return 即可 + case WxConsts.MenuButtonType.PIC_SYSPHOTO: + case WxConsts.MenuButtonType.PIC_PHOTO_OR_ALBUM: + case WxConsts.MenuButtonType.PIC_WEIXIN: + case WxConsts.MenuButtonType.LOCATION_SELECT: + return; + default: + log.error("[validateButton][未知的按钮({})]", button); + throw new IllegalArgumentException("不支持的按钮类型:" + type); + } + // 执行校验 + ValidationUtils.validate(validator, button, group); + } + + /** + * 根据消息类型,获得对应的媒体文件类型 + * + * 注意,不会返回 WxConsts.MediaFileType.THUMB,因为该类型会有明确标注 + * + * @param messageType 消息类型 {@link WxConsts.XmlMsgType} + * @return 媒体文件类型 {@link WxConsts.MediaFileType} + */ + public static String getMediaFileType(String messageType) { + switch (messageType) { + case WxConsts.XmlMsgType.IMAGE: + return WxConsts.MediaFileType.IMAGE; + case WxConsts.XmlMsgType.VOICE: + return WxConsts.MediaFileType.VOICE; + case WxConsts.XmlMsgType.VIDEO: + return WxConsts.MediaFileType.VIDEO; + default: + return WxConsts.MediaFileType.FILE; + } + } + + /** + * Text 类型的消息,参数校验 Group + */ + public interface TextMessageGroup {} + + /** + * Image 类型的消息,参数校验 Group + */ + public interface ImageMessageGroup {} + + /** + * Voice 类型的消息,参数校验 Group + */ + public interface VoiceMessageGroup {} + + /** + * Video 类型的消息,参数校验 Group + */ + public interface VideoMessageGroup {} + + /** + * News 类型的消息,参数校验 Group + */ + public interface NewsMessageGroup {} + + /** + * Music 类型的消息,参数校验 Group + */ + public interface MusicMessageGroup {} + + /** + * Click 类型的按钮,参数校验 Group + */ + public interface ClickButtonGroup {} + + /** + * View 类型的按钮,参数校验 Group + */ + public interface ViewButtonGroup {} + + /** + * MiniProgram 类型的按钮,参数校验 Group + */ + public interface MiniProgramButtonGroup {} + + /** + * SCANCODE_WAITMSG 类型的按钮,参数校验 Group + */ + public interface ScanCodeWaitMsgButtonGroup {} + + /** + * VIEW_LIMITED 类型的按钮,参数校验 Group + */ + public interface ViewLimitedButtonGroup {} +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/framework/package-info.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/framework/package-info.java new file mode 100644 index 0000000..112ecdd --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/framework/package-info.java @@ -0,0 +1,6 @@ +/** + * 属于 mp 模块的 framework 封装 + * + * @author 芋道源码 + */ +package cn.iocoder.yudao.module.mp.framework; diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/framework/web/config/MpWebConfiguration.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/framework/web/config/MpWebConfiguration.java new file mode 100644 index 0000000..75a2362 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/framework/web/config/MpWebConfiguration.java @@ -0,0 +1,24 @@ +package cn.iocoder.yudao.module.mp.framework.web.config; + +import cn.iocoder.yudao.framework.swagger.config.YudaoSwaggerAutoConfiguration; +import org.springdoc.core.models.GroupedOpenApi; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * mp 模块的 web 组件的 Configuration + * + * @author 芋道源码 + */ +@Configuration(proxyBeanMethods = false) +public class MpWebConfiguration { + + /** + * mp 模块的 API 分组 + */ + @Bean + public GroupedOpenApi mpGroupedOpenApi() { + return YudaoSwaggerAutoConfiguration.buildGroupedOpenApi("mp"); + } + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/framework/web/package-info.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/framework/web/package-info.java new file mode 100644 index 0000000..a103f1c --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/framework/web/package-info.java @@ -0,0 +1,4 @@ +/** + * mp 模块的 web 配置 + */ +package cn.iocoder.yudao.module.mp.framework.web; diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/package-info.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/package-info.java new file mode 100644 index 0000000..5987180 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/package-info.java @@ -0,0 +1,8 @@ +/** + * mp 模块,我们放微信微信公众号。 + * 例如说:提供微信公众号的账号、菜单、粉丝、标签、消息、自动回复、素材、模板通知、运营数据等功能 + * + * 1. Controller URL:以 /mp/ 开头,避免和其它 Module 冲突 + * 2. DataObject 表名:以 mp_ 开头,方便在数据库中区分 + */ +package cn.iocoder.yudao.module.mp; diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/account/MpAccountService.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/account/MpAccountService.java new file mode 100644 index 0000000..1d7296d --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/account/MpAccountService.java @@ -0,0 +1,110 @@ +package cn.iocoder.yudao.module.mp.service.account; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountCreateReqVO; +import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountPageReqVO; +import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountUpdateReqVO; +import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; + +import jakarta.validation.Valid; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.ACCOUNT_NOT_EXISTS; + +/** + * 公众号账号 Service 接口 + * + * @author 芋道源码 + */ +public interface MpAccountService { + + /** + * 初始化缓存 + */ + void initLocalCache(); + + /** + * 创建公众号账号 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createAccount(@Valid MpAccountCreateReqVO createReqVO); + + /** + * 更新公众号账号 + * + * @param updateReqVO 更新信息 + */ + void updateAccount(@Valid MpAccountUpdateReqVO updateReqVO); + + /** + * 删除公众号账号 + * + * @param id 编号 + */ + void deleteAccount(Long id); + + /** + * 获得公众号账号 + * + * @param id 编号 + * @return 公众号账号 + */ + MpAccountDO getAccount(Long id); + + /** + * 获得公众号账号。若不存在,则抛出业务异常 + * + * @param id 编号 + * @return 公众号账号 + */ + default MpAccountDO getRequiredAccount(Long id) { + MpAccountDO account = getAccount(id); + if (account == null) { + throw exception(ACCOUNT_NOT_EXISTS); + } + return account; + } + + /** + * 从缓存中,获得公众号账号 + * + * @param appId 微信公众号 appId + * @return 公众号账号 + */ + MpAccountDO getAccountFromCache(String appId); + + /** + * 获得公众号账号分页 + * + * @param pageReqVO 分页查询 + * @return 公众号账号分页 + */ + PageResult getAccountPage(MpAccountPageReqVO pageReqVO); + + /** + * 获得公众号账号列表 + * + * @return 公众号账号列表 + */ + List getAccountList(); + + /** + * 生成公众号账号的二维码 + * + * @param id 编号 + */ + void generateAccountQrCode(Long id); + + /** + * 清空公众号账号的 API 配额 + * + * 参考文档:接口调用频次限制说明 + * + * @param id 编号 + */ + void clearAccountQuota(Long id); + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/account/MpAccountServiceImpl.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/account/MpAccountServiceImpl.java new file mode 100644 index 0000000..14e91b6 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/account/MpAccountServiceImpl.java @@ -0,0 +1,229 @@ +package cn.iocoder.yudao.module.mp.service.account; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjUtil; +import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; +import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountCreateReqVO; +import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountPageReqVO; +import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountUpdateReqVO; +import cn.iocoder.yudao.module.mp.convert.account.MpAccountConvert; +import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; +import cn.iocoder.yudao.module.mp.dal.mysql.account.MpAccountMapper; +import cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants; +import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory; +import com.google.common.annotations.VisibleForTesting; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.result.WxMpQrCodeTicket; +import org.springframework.context.annotation.Lazy; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import jakarta.annotation.PostConstruct; +import jakarta.annotation.Resource; +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getMaxValue; +import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.USER_USERNAME_EXISTS; + +/** + * 公众号账号 Service 实现类 + * + * @author fengdan + */ +@Slf4j +@Service +@Validated +public class MpAccountServiceImpl implements MpAccountService { + + /** + * 账号缓存 + * key:账号编号 {@link MpAccountDO#getAppId()} + * + * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向 + */ + @Getter + private volatile Map accountCache; + + @Resource + private MpAccountMapper mpAccountMapper; + + @Resource + @Lazy // 延迟加载,解决循环依赖的问题 + private MpServiceFactory mpServiceFactory; + + @Override + @PostConstruct + public void initLocalCache() { + // 注意:忽略自动多租户,因为要全局初始化缓存 + TenantUtils.executeIgnore(() -> { + // 第一步:查询数据 + List accounts = Collections.emptyList(); + try { + accounts = mpAccountMapper.selectList(); + } catch (Throwable ex) { + if (!ex.getMessage().contains("doesn't exist")) { + throw ex; + } + log.error("[微信公众号 yudao-module-mp - 表结构未导入][参考 https://doc.iocoder.cn/mp/build/ 开启]"); + } + log.info("[initLocalCacheIfUpdate][缓存公众号账号,数量为:{}]", accounts.size()); + + // 第二步:构建缓存。创建或更新支付 Client + mpServiceFactory.init(accounts); + accountCache = convertMap(accounts, MpAccountDO::getAppId); + }); + } + + /** + * 通过定时任务轮询,刷新缓存 + * + * 目的:多节点部署时,通过轮询”通知“所有节点,进行刷新 + */ + @Scheduled(initialDelay = 60, fixedRate = 60, timeUnit = TimeUnit.SECONDS) + public void refreshLocalCache() { + // 注意:忽略自动多租户,因为要全局初始化缓存 + TenantUtils.executeIgnore(() -> { + // 情况一:如果缓存里没有数据,则直接刷新缓存 + if (CollUtil.isEmpty(accountCache)) { + initLocalCache(); + return; + } + + // 情况二,如果缓存里数据,则通过 updateTime 判断是否有数据变更,有变更则刷新缓存 + LocalDateTime maxTime = getMaxValue(accountCache.values(), MpAccountDO::getUpdateTime); + if (mpAccountMapper.selectCountByUpdateTimeGt(maxTime) > 0) { + initLocalCache(); + } + }); + } + + @Override + public Long createAccount(MpAccountCreateReqVO createReqVO) { + // 校验 appId 唯一 + validateAppIdUnique(null, createReqVO.getAppId()); + + // 插入 + MpAccountDO account = MpAccountConvert.INSTANCE.convert(createReqVO); + mpAccountMapper.insert(account); + + // 刷新缓存 + initLocalCache(); + return account.getId(); + } + + @Override + public void updateAccount(MpAccountUpdateReqVO updateReqVO) { + // 校验存在 + validateAccountExists(updateReqVO.getId()); + // 校验 appId 唯一 + validateAppIdUnique(updateReqVO.getId(), updateReqVO.getAppId()); + + // 更新 + MpAccountDO updateObj = MpAccountConvert.INSTANCE.convert(updateReqVO); + mpAccountMapper.updateById(updateObj); + + // 刷新缓存 + initLocalCache(); + } + + @Override + public void deleteAccount(Long id) { + // 校验存在 + validateAccountExists(id); + // 删除 + mpAccountMapper.deleteById(id); + + // 刷新缓存 + initLocalCache(); + } + + private MpAccountDO validateAccountExists(Long id) { + MpAccountDO account = mpAccountMapper.selectById(id); + if (account == null) { + throw ServiceExceptionUtil.exception(ErrorCodeConstants.ACCOUNT_NOT_EXISTS); + } + return account; + } + + @VisibleForTesting + public void validateAppIdUnique(Long id, String appId) { + // 多个租户,appId 是不能重复,否则公众号回调会无法识别 + TenantUtils.executeIgnore(() -> { + MpAccountDO account = mpAccountMapper.selectByAppId(appId); + if (account == null) { + return; + } + // 存在 account 记录的情况下 + if (id == null // 新增时,说明重复 + || ObjUtil.notEqual(id, account.getId())) { // 更新时,如果 id 不一致,说明重复 + throw exception(USER_USERNAME_EXISTS); + } + }); + } + + @Override + public MpAccountDO getAccount(Long id) { + return mpAccountMapper.selectById(id); + } + + @Override + public MpAccountDO getAccountFromCache(String appId) { + return accountCache.get(appId); + } + + @Override + public PageResult getAccountPage(MpAccountPageReqVO pageReqVO) { + return mpAccountMapper.selectPage(pageReqVO); + } + + @Override + public List getAccountList() { + return mpAccountMapper.selectList(); + } + + @Override + public void generateAccountQrCode(Long id) { + // 校验存在 + MpAccountDO account = validateAccountExists(id); + + // 生成二维码 + WxMpService mpService = mpServiceFactory.getRequiredMpService(account.getAppId()); + String qrCodeUrl; + try { + WxMpQrCodeTicket qrCodeTicket = mpService.getQrcodeService().qrCodeCreateLastTicket("default"); + qrCodeUrl = mpService.getQrcodeService().qrCodePictureUrl(qrCodeTicket.getTicket()); + } catch (WxErrorException e) { + throw exception(ErrorCodeConstants.ACCOUNT_GENERATE_QR_CODE_FAIL, e.getError().getErrorMsg()); + } + + // 保存二维码 + mpAccountMapper.updateById(new MpAccountDO().setId(id).setQrCodeUrl(qrCodeUrl)); + } + + @Override + public void clearAccountQuota(Long id) { + // 校验存在 + MpAccountDO account = validateAccountExists(id); + + // 生成二维码 + WxMpService mpService = mpServiceFactory.getRequiredMpService(account.getAppId()); + try { + mpService.clearQuota(account.getAppId()); + } catch (WxErrorException e) { + throw exception(ErrorCodeConstants.ACCOUNT_CLEAR_QUOTA_FAIL, e.getError().getErrorMsg()); + } + } + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/menu/MenuHandler.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/menu/MenuHandler.java new file mode 100644 index 0000000..83f9517 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/menu/MenuHandler.java @@ -0,0 +1,34 @@ +package cn.iocoder.yudao.module.mp.service.handler.menu; + +import cn.iocoder.yudao.module.mp.framework.mp.core.context.MpContextHolder; +import cn.iocoder.yudao.module.mp.service.menu.MpMenuService; +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpMessageHandler; +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 jakarta.annotation.Resource; +import java.util.Map; + +/** + * 自定义菜单的事件处理器 + * + * 逻辑:粉丝点击菜单时,触发对应的回复 + * + * @author 芋道源码 + */ +@Component +public class MenuHandler implements WxMpMessageHandler { + + @Resource + private MpMenuService mpMenuService; + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map context, + WxMpService weixinService, WxSessionManager sessionManager) { + return mpMenuService.reply(MpContextHolder.getAppId(), wxMessage.getEventKey(), wxMessage.getFromUser()); + } + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/message/MessageAutoReplyHandler.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/message/MessageAutoReplyHandler.java new file mode 100644 index 0000000..7e24a83 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/message/MessageAutoReplyHandler.java @@ -0,0 +1,41 @@ +package cn.iocoder.yudao.module.mp.service.handler.message; + +import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpAutoReplyDO; +import cn.iocoder.yudao.module.mp.framework.mp.core.context.MpContextHolder; +import cn.iocoder.yudao.module.mp.service.message.MpAutoReplyService; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpMessageHandler; +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 jakarta.annotation.Resource; +import java.util.Map; + +/** + * 自动回复消息的事件处理器 + * + * @author 芋道源码 + */ +@Component +@Slf4j +public class MessageAutoReplyHandler implements WxMpMessageHandler { + + @Resource + private MpAutoReplyService mpAutoReplyService; + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map context, + WxMpService weixinService, WxSessionManager sessionManager) { + // 只处理指定类型的消息 + if (!MpAutoReplyDO.REQUEST_MESSAGE_TYPE.contains(wxMessage.getMsgType())) { + return null; + } + + // 自动回复 + return mpAutoReplyService.replyForMessage(MpContextHolder.getAppId(), wxMessage); + } + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/message/MessageReceiveHandler.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/message/MessageReceiveHandler.java new file mode 100644 index 0000000..1a4605b --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/message/MessageReceiveHandler.java @@ -0,0 +1,36 @@ +package cn.iocoder.yudao.module.mp.service.handler.message; + +import cn.iocoder.yudao.module.mp.framework.mp.core.context.MpContextHolder; +import cn.iocoder.yudao.module.mp.service.message.MpMessageService; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpMessageHandler; +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 jakarta.annotation.Resource; +import java.util.Map; + +/** + * 保存微信消息的事件处理器 + * + * @author 芋道源码 + */ +@Component +@Slf4j +public class MessageReceiveHandler implements WxMpMessageHandler { + + @Resource + private MpMessageService mpMessageService; + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map context, + WxMpService wxMpService, WxSessionManager sessionManager) { + log.info("[handle][接收到请求消息,内容:{}]", wxMessage); + mpMessageService.receiveMessage(wxMpService, MpContextHolder.getAppId(), wxMessage); + return null; + } + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/KfSessionHandler.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/KfSessionHandler.java new file mode 100644 index 0000000..fca7fab --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/KfSessionHandler.java @@ -0,0 +1,26 @@ +package cn.iocoder.yudao.module.mp.service.handler.other; + +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpMessageHandler; +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 芋道源码 + */ +@Component +public class KfSessionHandler implements WxMpMessageHandler { + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map context, + WxMpService wxMpService, WxSessionManager sessionManager) { + throw new UnsupportedOperationException("未实现该处理,请自行重写"); + } + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/NullHandler.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/NullHandler.java new file mode 100644 index 0000000..09b3efb --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/NullHandler.java @@ -0,0 +1,24 @@ +package cn.iocoder.yudao.module.mp.service.handler.other; + +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpMessageHandler; +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; + +/** + * 点击菜单连接的事件处理器 + */ +@Component +public class NullHandler implements WxMpMessageHandler { + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map context, + WxMpService wxMpService, WxSessionManager sessionManager) { + throw new UnsupportedOperationException("未实现该处理,请自行重写"); + } + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/ScanHandler.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/ScanHandler.java new file mode 100644 index 0000000..54a6bbc --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/ScanHandler.java @@ -0,0 +1,25 @@ +package cn.iocoder.yudao.module.mp.service.handler.other; + +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpMessageHandler; +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; + +/** + * 扫码的事件处理器 + */ +@Component +public class ScanHandler implements WxMpMessageHandler { + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMpXmlMessage, Map context, + WxMpService wxMpService, WxSessionManager wxSessionManager) throws WxErrorException { + throw new UnsupportedOperationException("未实现该处理,请自行重写"); + } + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/StoreCheckNotifyHandler.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/StoreCheckNotifyHandler.java new file mode 100644 index 0000000..475b8ee --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/StoreCheckNotifyHandler.java @@ -0,0 +1,24 @@ +package cn.iocoder.yudao.module.mp.service.handler.other; + +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpMessageHandler; +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; + +/** + * 门店审核事件的事件处理器 + */ +@Component +public class StoreCheckNotifyHandler implements WxMpMessageHandler { + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map context, + WxMpService wxMpService, WxSessionManager sessionManager) { + throw new UnsupportedOperationException("未实现该处理,请自行重写"); + } + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/package-info.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/package-info.java new file mode 100644 index 0000000..7fd6622 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/package-info.java @@ -0,0 +1,4 @@ +/** + * 本包内的 handler 都是一些不重要的,所以放在 other 其它里 + */ +package cn.iocoder.yudao.module.mp.service.handler.other; diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/user/LocationHandler.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/user/LocationHandler.java new file mode 100644 index 0000000..ebed43f --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/user/LocationHandler.java @@ -0,0 +1,49 @@ +package cn.iocoder.yudao.module.mp.service.handler.user; + +import cn.hutool.core.util.ObjectUtil; +import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; +import cn.iocoder.yudao.module.mp.framework.mp.core.context.MpContextHolder; +import cn.iocoder.yudao.module.mp.service.message.MpAutoReplyService; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpMessageHandler; +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 jakarta.annotation.Resource; +import java.util.Map; + +/** + * 上报地理位置的事件处理器 + * + * 触发操作:打开微信公众号 -> 点击 + 号 -> 选择「语音」 + * + * 逻辑:粉丝上传地理位置时,也可以触发自动回复 + * + * @author 芋道源码 + */ +@Component +@Slf4j +public class LocationHandler implements WxMpMessageHandler { + + @Resource + private MpAutoReplyService mpAutoReplyService; + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map context, + WxMpService wxMpService, WxSessionManager sessionManager) { + // 防御性编程:必须是 LOCATION 消息 + if (ObjectUtil.notEqual(wxMessage.getMsgType(), WxConsts.XmlMsgType.LOCATION)) { + return null; + } + log.info("[handle][上报地理位置,纬度({})、经度({})、精度({})", wxMessage.getLatitude(), + wxMessage.getLongitude(), wxMessage.getPrecision()); + + // 自动回复 + return mpAutoReplyService.replyForMessage(MpContextHolder.getAppId(), wxMessage); + } + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/user/SubscribeHandler.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/user/SubscribeHandler.java new file mode 100644 index 0000000..1454083 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/user/SubscribeHandler.java @@ -0,0 +1,52 @@ +package cn.iocoder.yudao.module.mp.service.handler.user; + +import cn.iocoder.yudao.module.mp.framework.mp.core.context.MpContextHolder; +import cn.iocoder.yudao.module.mp.service.message.MpAutoReplyService; +import cn.iocoder.yudao.module.mp.service.user.MpUserService; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpMessageHandler; +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.stereotype.Component; + +import jakarta.annotation.Resource; +import java.util.Map; + +/** + * 关注的事件处理器 + * + * @author 芋道源码 + */ +@Component +@Slf4j +public class SubscribeHandler implements WxMpMessageHandler { + + @Resource + private MpUserService mpUserService; + @Resource + private MpAutoReplyService mpAutoReplyService; + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map context, + WxMpService weixinService, WxSessionManager sessionManager) throws WxErrorException { + // 第一步,从公众号平台,获取粉丝信息 + log.info("[handle][粉丝({}) 关注]", wxMessage.getFromUser()); + WxMpUser wxMpUser = null; + try { + wxMpUser = weixinService.getUserService().userInfo(wxMessage.getFromUser()); + } catch (WxErrorException e) { + log.error("[handle][粉丝({})] 获取粉丝信息失败!", wxMessage.getFromUser(), e); + } + + // 第二步,保存粉丝信息 + mpUserService.saveUser(MpContextHolder.getAppId(), wxMpUser); + + // 第三步,回复关注的欢迎语 + return mpAutoReplyService.replyForSubscribe(MpContextHolder.getAppId(), wxMessage); + } + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/user/UnsubscribeHandler.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/user/UnsubscribeHandler.java new file mode 100644 index 0000000..e1afb1e --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/handler/user/UnsubscribeHandler.java @@ -0,0 +1,39 @@ +package cn.iocoder.yudao.module.mp.service.handler.user; + +import cn.iocoder.yudao.module.mp.framework.mp.core.context.MpContextHolder; +import cn.iocoder.yudao.module.mp.service.user.MpUserService; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpMessageHandler; +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.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import jakarta.annotation.Resource; +import java.util.Map; + +/** + * 取消关注的事件处理器 + * + * @author 芋道源码 + */ +@Component +@Slf4j +public class UnsubscribeHandler implements WxMpMessageHandler { + + @Resource + @Lazy // 延迟加载,解决循环依赖的问题 + private MpUserService mpUserService; + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, + Map context, WxMpService wxMpService, + WxSessionManager sessionManager) { + log.info("[handle][粉丝({}) 取消关注]", wxMessage.getFromUser()); + mpUserService.updateUserUnsubscribe(MpContextHolder.getAppId(), wxMessage.getFromUser()); + return null; + } + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/material/MpMaterialService.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/material/MpMaterialService.java new file mode 100644 index 0000000..a6a0041 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/material/MpMaterialService.java @@ -0,0 +1,84 @@ +package cn.iocoder.yudao.module.mp.service.material; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.mp.controller.admin.material.vo.MpMaterialPageReqVO; +import cn.iocoder.yudao.module.mp.controller.admin.material.vo.MpMaterialUploadNewsImageReqVO; +import cn.iocoder.yudao.module.mp.controller.admin.material.vo.MpMaterialUploadPermanentReqVO; +import cn.iocoder.yudao.module.mp.controller.admin.material.vo.MpMaterialUploadTemporaryReqVO; +import cn.iocoder.yudao.module.mp.dal.dataobject.material.MpMaterialDO; +import me.chanjar.weixin.common.api.WxConsts; + +import jakarta.validation.Valid; +import java.io.IOException; +import java.util.Collection; +import java.util.List; + +/** + * 公众号素材 Service 接口 + * + * @author 芋道源码 + */ +public interface MpMaterialService { + + /** + * 获得素材的 URL + * + * 该 URL 来自我们自己的文件服务器存储的 URL,不是公众号存储的 URL + * + * @param accountId 公众号账号编号 + * @param mediaId 公众号素材 id + * @param type 文件类型 {@link WxConsts.MediaFileType} + * @return 素材的 URL + */ + String downloadMaterialUrl(Long accountId, String mediaId, String type); + + /** + * 上传临时素材 + * + * @param reqVO 请求 + * @return 素材 + * @throws IOException 文件操作发生异常 + */ + MpMaterialDO uploadTemporaryMaterial(@Valid MpMaterialUploadTemporaryReqVO reqVO) throws IOException; + + /** + * 上传永久素材 + * + * @param reqVO 请求 + * @return 素材 + * @throws IOException 文件操作发生异常 + */ + MpMaterialDO uploadPermanentMaterial(@Valid MpMaterialUploadPermanentReqVO reqVO) throws IOException; + + /** + * 上传图文内容中的图片 + * + * @param reqVO 上传请求 + * @return 图片地址 + */ + String uploadNewsImage(MpMaterialUploadNewsImageReqVO reqVO) throws IOException; + + /** + * 获得素材分页 + * + * @param pageReqVO 分页请求 + * @return 素材分页 + */ + PageResult getMaterialPage(MpMaterialPageReqVO pageReqVO); + + /** + * 获得素材列表 + * + * @param mediaIds 素材 mediaId 列表 + * @return 素材列表 + */ + List getMaterialListByMediaId(Collection mediaIds); + + /** + * 删除素材 + * + * @param id 编号 + */ + void deleteMaterial(Long id); + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/material/MpMaterialServiceImpl.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/material/MpMaterialServiceImpl.java new file mode 100644 index 0000000..7241f88 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/material/MpMaterialServiceImpl.java @@ -0,0 +1,224 @@ +package cn.iocoder.yudao.module.mp.service.material; + +import cn.hutool.core.io.FileTypeUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.ObjUtil; +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.infra.api.file.FileApi; +import cn.iocoder.yudao.module.mp.controller.admin.material.vo.MpMaterialPageReqVO; +import cn.iocoder.yudao.module.mp.controller.admin.material.vo.MpMaterialUploadNewsImageReqVO; +import cn.iocoder.yudao.module.mp.controller.admin.material.vo.MpMaterialUploadPermanentReqVO; +import cn.iocoder.yudao.module.mp.controller.admin.material.vo.MpMaterialUploadTemporaryReqVO; +import cn.iocoder.yudao.module.mp.convert.material.MpMaterialConvert; +import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; +import cn.iocoder.yudao.module.mp.dal.dataobject.material.MpMaterialDO; +import cn.iocoder.yudao.module.mp.dal.mysql.material.MpMaterialMapper; +import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory; +import cn.iocoder.yudao.module.mp.service.account.MpAccountService; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.material.WxMpMaterialUploadResult; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import jakarta.annotation.Resource; +import java.io.File; +import java.io.IOException; +import java.util.Collection; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.*; + +/** + * 公众号素材 Service 接口 + * + * @author 芋道源码 + */ +@Service +@Validated +@Slf4j +public class MpMaterialServiceImpl implements MpMaterialService { + + @Resource + private MpMaterialMapper mpMaterialMapper; + + @Resource + private FileApi fileApi; + + @Resource + @Lazy // 延迟加载,解决循环依赖的问题 + private MpAccountService mpAccountService; + + @Resource + @Lazy // 延迟加载,解决循环依赖的问题 + private MpServiceFactory mpServiceFactory; + + @Override + public String downloadMaterialUrl(Long accountId, String mediaId, String type) { + // 第一步,直接从数据库查询。如果已经下载,直接返回 + MpMaterialDO material = mpMaterialMapper.selectByAccountIdAndMediaId(accountId, mediaId); + if (material != null) { + return material.getUrl(); + } + + // 第二步,尝试从临时素材中下载 + String url = downloadMedia(accountId, mediaId); + if (url == null) { + return null; + } + MpAccountDO account = mpAccountService.getRequiredAccount(accountId); + material = MpMaterialConvert.INSTANCE.convert(mediaId, type, url, account, null) + .setPermanent(false); + mpMaterialMapper.insert(material); + + // 不考虑下载永久素材,因为上传的时候已经保存 + return url; + } + + @Override + public MpMaterialDO uploadTemporaryMaterial(MpMaterialUploadTemporaryReqVO reqVO) throws IOException { + WxMpService mpService = mpServiceFactory.getRequiredMpService(reqVO.getAccountId()); + // 第一步,上传到公众号 + File file = null; + WxMediaUploadResult result; + String mediaId; + String url; + try { + // 写入到临时文件 + file = FileUtil.newFile(FileUtil.getTmpDirPath() + reqVO.getFile().getOriginalFilename()); + reqVO.getFile().transferTo(file); + // 上传到公众号 + result = mpService.getMaterialService().mediaUpload(reqVO.getType(), file); + // 上传到文件服务 + mediaId = ObjUtil.defaultIfNull(result.getMediaId(), result.getThumbMediaId()); + url = uploadFile(mediaId, file); + } catch (WxErrorException e) { + throw exception(MATERIAL_UPLOAD_FAIL, e.getError().getErrorMsg()); + } finally { + FileUtil.del(file); + } + + // 第二步,存储到数据库 + MpAccountDO account = mpAccountService.getRequiredAccount(reqVO.getAccountId()); + MpMaterialDO material = MpMaterialConvert.INSTANCE.convert(mediaId, reqVO.getType(), url, account, + reqVO.getFile().getName()).setPermanent(false); + mpMaterialMapper.insert(material); + return material; + } + + @Override + public MpMaterialDO uploadPermanentMaterial(MpMaterialUploadPermanentReqVO reqVO) throws IOException { + WxMpService mpService = mpServiceFactory.getRequiredMpService(reqVO.getAccountId()); + // 第一步,上传到公众号 + String name = StrUtil.blankToDefault(reqVO.getName(), reqVO.getFile().getName()); + File file = null; + WxMpMaterialUploadResult result; + String mediaId; + String url; + try { + // 写入到临时文件 + file = FileUtil.newFile(FileUtil.getTmpDirPath() + reqVO.getFile().getOriginalFilename()); + reqVO.getFile().transferTo(file); + // 上传到公众号 + result = mpService.getMaterialService().materialFileUpload(reqVO.getType(), + MpMaterialConvert.INSTANCE.convert(name, file, reqVO.getTitle(), reqVO.getIntroduction())); + // 上传到文件服务 + mediaId = ObjUtil.defaultIfNull(result.getMediaId(), result.getMediaId()); + url = uploadFile(mediaId, file); + } catch (WxErrorException e) { + throw exception(MATERIAL_UPLOAD_FAIL, e.getError().getErrorMsg()); + } finally { + FileUtil.del(file); + } + + // 第二步,存储到数据库 + MpAccountDO account = mpAccountService.getRequiredAccount(reqVO.getAccountId()); + MpMaterialDO material = MpMaterialConvert.INSTANCE.convert(mediaId, reqVO.getType(), url, account, + name, reqVO.getTitle(), reqVO.getIntroduction(), result.getUrl()).setPermanent(true); + mpMaterialMapper.insert(material); + return material; + } + + @Override + public String uploadNewsImage(MpMaterialUploadNewsImageReqVO reqVO) throws IOException { + WxMpService mpService = mpServiceFactory.getRequiredMpService(reqVO.getAccountId()); + File file = null; + try { + // 写入到临时文件 + file = FileUtil.newFile(FileUtil.getTmpDirPath() + reqVO.getFile().getOriginalFilename()); + reqVO.getFile().transferTo(file); + // 上传到公众号 + return mpService.getMaterialService().mediaImgUpload(file).getUrl(); + } catch (WxErrorException e) { + throw exception(MATERIAL_IMAGE_UPLOAD_FAIL, e.getError().getErrorMsg()); + } finally { + FileUtil.del(file); + } + } + + @Override + public PageResult getMaterialPage(MpMaterialPageReqVO pageReqVO) { + return mpMaterialMapper.selectPage(pageReqVO); + } + + @Override + public List getMaterialListByMediaId(Collection mediaIds) { + return mpMaterialMapper.selectListByMediaId(mediaIds); + } + + @Override + public void deleteMaterial(Long id) { + MpMaterialDO material = mpMaterialMapper.selectById(id); + if (material == null) { + throw exception(MATERIAL_NOT_EXISTS); + } + + // 第一步,从公众号删除 + if (material.getPermanent()) { + WxMpService mpService = mpServiceFactory.getRequiredMpService(material.getAppId()); + try { + mpService.getMaterialService().materialDelete(material.getMediaId()); + } catch (WxErrorException e) { + throw exception(MATERIAL_DELETE_FAIL, e.getError().getErrorMsg()); + } + } + + // 第二步,从数据库中删除 + mpMaterialMapper.deleteById(id); + } + + /** + * 下载微信媒体文件的内容,并上传到文件服务 + * + * 为什么要下载?媒体文件在微信后台保存时间为 3 天,即 3 天后 media_id 失效。 + * + * @param accountId 公众号账号的编号 + * @param mediaId 媒体文件编号 + * @return 上传后的 URL + */ + public String downloadMedia(Long accountId, String mediaId) { + WxMpService mpService = mpServiceFactory.getMpService(accountId); + for (int i = 0; i < 3; i++) { + try { + // 第一步,从公众号下载媒体文件 + File file = mpService.getMaterialService().mediaDownload(mediaId); + // 第二步,上传到文件服务 + return uploadFile(mediaId, file); + } catch (WxErrorException e) { + log.error("[mediaDownload][media({}) 第 ({}) 次下载失败]", mediaId, i); + } + } + return null; + } + + private String uploadFile(String mediaId, File file) { + String path = mediaId + "." + FileTypeUtil.getType(file); + return fileApi.createFile(FileUtil.readBytes(file), path); + } + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/menu/MpMenuService.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/menu/MpMenuService.java new file mode 100644 index 0000000..0b2fc45 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/menu/MpMenuService.java @@ -0,0 +1,49 @@ +package cn.iocoder.yudao.module.mp.service.menu; + +import cn.iocoder.yudao.module.mp.controller.admin.menu.vo.MpMenuSaveReqVO; +import cn.iocoder.yudao.module.mp.dal.dataobject.menu.MpMenuDO; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; + +import jakarta.validation.Valid; +import java.util.List; + +/** + * 公众号菜单 Service 接口 + * + * @author 芋道源码 + */ +public interface MpMenuService { + + /** + * 保存公众号菜单 + * + * @param createReqVO 创建信息 + */ + void saveMenu(@Valid MpMenuSaveReqVO createReqVO); + + /** + * 删除公众号菜单 + * + * @param accountId 公众号账号的编号 + */ + void deleteMenuByAccountId(Long accountId); + + /** + * 粉丝点击菜单按钮时,回复对应的消息 + * + * @param appId 公众号 AppId + * @param key 菜单按钮的标识 + * @param openid 粉丝的 openid + * @return 消息 + */ + WxMpXmlOutMessage reply(String appId, String key, String openid); + + /** + * 获得公众号菜单列表 + * + * @param accountId 公众号账号的编号 + * @return 公众号菜单列表 + */ + List getMenuListByAccountId(Long accountId); + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/menu/MpMenuServiceImpl.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/menu/MpMenuServiceImpl.java new file mode 100644 index 0000000..1afe47c --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/menu/MpMenuServiceImpl.java @@ -0,0 +1,171 @@ +package cn.iocoder.yudao.module.mp.service.menu; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.module.mp.controller.admin.menu.vo.MpMenuSaveReqVO; +import cn.iocoder.yudao.module.mp.convert.menu.MpMenuConvert; +import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; +import cn.iocoder.yudao.module.mp.dal.dataobject.menu.MpMenuDO; +import cn.iocoder.yudao.module.mp.dal.mysql.menu.MpMenuMapper; +import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory; +import cn.iocoder.yudao.module.mp.framework.mp.core.util.MpUtils; +import cn.iocoder.yudao.module.mp.service.account.MpAccountService; +import cn.iocoder.yudao.module.mp.service.message.MpMessageService; +import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.bean.menu.WxMenu; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import jakarta.annotation.Resource; +import jakarta.validation.Validator; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.MENU_DELETE_FAIL; +import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.MENU_SAVE_FAIL; + +/** + * 公众号菜单 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +@Slf4j +public class MpMenuServiceImpl implements MpMenuService { + + @Resource + private MpMessageService mpMessageService; + @Resource + @Lazy // 延迟加载,避免循环引用报错 + private MpAccountService mpAccountService; + + @Resource + @Lazy // 延迟加载,避免循环引用报错 + private MpServiceFactory mpServiceFactory; + + @Resource + private Validator validator; + + @Resource + private MpMenuMapper mpMenuMapper; + + @Override + @Transactional(rollbackFor = Exception.class) + public void saveMenu(MpMenuSaveReqVO createReqVO) { + MpAccountDO account = mpAccountService.getRequiredAccount(createReqVO.getAccountId()); + WxMpService mpService = mpServiceFactory.getRequiredMpService(createReqVO.getAccountId()); + + // 参数校验 + createReqVO.getMenus().forEach(this::validateMenu); + + // 第一步,同步公众号 + WxMenu wxMenu = new WxMenu(); + wxMenu.setButtons(MpMenuConvert.INSTANCE.convert(createReqVO.getMenus())); + try { + mpService.getMenuService().menuCreate(wxMenu); + } catch (WxErrorException e) { + throw exception(MENU_SAVE_FAIL, e.getError().getErrorMsg()); + } + + // 第二步,存储到数据库 + mpMenuMapper.deleteByAccountId(createReqVO.getAccountId()); + createReqVO.getMenus().forEach(menu -> { + // 先保存顶级菜单 + MpMenuDO menuDO = createMenu(menu, null, account); + // 再保存子菜单 + if (CollUtil.isEmpty(menu.getChildren())) { + return; + } + menu.getChildren().forEach(childMenu -> createMenu(childMenu, menuDO, account)); + }); + } + + /** + * 校验菜单的格式是否正确 + * + * @param menu 菜单 + */ + private void validateMenu(MpMenuSaveReqVO.Menu menu) { + MpUtils.validateButton(validator, menu.getType(), menu.getReplyMessageType(), menu); + // 子菜单 + if (CollUtil.isEmpty(menu.getChildren())) { + return; + } + menu.getChildren().forEach(this::validateMenu); + } + + /** + * 创建菜单,并存储到数据库 + * + * @param wxMenu 菜单信息 + * @param parentMenu 父菜单 + * @param account 公众号账号 + * @return 创建后的菜单 + */ + private MpMenuDO createMenu(MpMenuSaveReqVO.Menu wxMenu, MpMenuDO parentMenu, MpAccountDO account) { + // 创建菜单 + MpMenuDO menu = CollUtil.isNotEmpty(wxMenu.getChildren()) + ? new MpMenuDO().setName(wxMenu.getName()) + : MpMenuConvert.INSTANCE.convert02(wxMenu); + // 设置菜单的公众号账号信息 + if (account != null) { + menu.setAccountId(account.getId()).setAppId(account.getAppId()); + } + // 设置父编号 + if (parentMenu != null) { + menu.setParentId(parentMenu.getId()); + } else { + menu.setParentId(MpMenuDO.ID_ROOT); + } + + // 插入到数据库 + mpMenuMapper.insert(menu); + return menu; + } + + @Override + public void deleteMenuByAccountId(Long accountId) { + WxMpService mpService = mpServiceFactory.getRequiredMpService(accountId); + // 第一步,同步公众号 + try { + mpService.getMenuService().menuDelete(); + } catch (WxErrorException e) { + throw exception(MENU_DELETE_FAIL, e.getError().getErrorMsg()); + } + + // 第二步,存储到数据库 + mpMenuMapper.deleteByAccountId(accountId); + } + + @Override + public WxMpXmlOutMessage reply(String appId, String key, String openid) { + // 第一步,获得菜单 + MpMenuDO menu = mpMenuMapper.selectByAppIdAndMenuKey(appId, key); + if (menu == null) { + log.error("[reply][appId({}) key({}) 找不到对应的菜单]", appId, key); + return null; + } + // 按钮必须要有消息类型,不然后续无法回复消息 + if (StrUtil.isEmpty(menu.getReplyMessageType())) { + log.error("[reply][menu({}) 不存在对应的消息类型]", menu); + return null; + } + + // 第二步,回复消息 + MpMessageSendOutReqBO sendReqBO = MpMenuConvert.INSTANCE.convert(openid, menu); + return mpMessageService.sendOutMessage(sendReqBO); + } + + @Override + public List getMenuListByAccountId(Long accountId) { + return mpMenuMapper.selectListByAccountId(accountId); + } + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpAutoReplyService.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpAutoReplyService.java new file mode 100644 index 0000000..fc125d0 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpAutoReplyService.java @@ -0,0 +1,75 @@ +package cn.iocoder.yudao.module.mp.service.message; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.mp.controller.admin.message.vo.autoreply.MpAutoReplyCreateReqVO; +import cn.iocoder.yudao.module.mp.controller.admin.message.vo.autoreply.MpAutoReplyUpdateReqVO; +import cn.iocoder.yudao.module.mp.controller.admin.message.vo.message.MpMessagePageReqVO; +import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpAutoReplyDO; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; + +/** + * 公众号的自动回复 Service 接口 + * + * @author 芋道源码 + */ +public interface MpAutoReplyService { + + /** + * 获得公众号自动回复分页 + * + * @param pageVO 分页请求 + * @return 自动回复分页结果 + */ + PageResult getAutoReplyPage(MpMessagePageReqVO pageVO); + + /** + * 获得公众号自动回复 + * + * @param id 编号 + * @return 自动回复 + */ + MpAutoReplyDO getAutoReply(Long id); + + + /** + * 创建公众号自动回复 + * + * @param createReqVO 创建请求 + * @return 自动回复的编号 + */ + Long createAutoReply(MpAutoReplyCreateReqVO createReqVO); + + /** + * 更新公众号自动回复 + * + * @param updateReqVO 更新请求 + */ + void updateAutoReply(MpAutoReplyUpdateReqVO updateReqVO); + + /** + * 删除公众号自动回复 + * + * @param id 自动回复的编号 + */ + void deleteAutoReply(Long id); + + /** + * 当收到消息时,自动回复 + * + * @param appId 微信公众号 appId + * @param wxMessage 消息 + * @return 回复的消息 + */ + WxMpXmlOutMessage replyForMessage(String appId, WxMpXmlMessage wxMessage); + + /** + * 当粉丝关注时,自动回复 + * + * @param appId 微信公众号 appId + * @param wxMessage 消息 + * @return 回复的消息 + */ + WxMpXmlOutMessage replyForSubscribe(String appId, WxMpXmlMessage wxMessage); + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpAutoReplyServiceImpl.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpAutoReplyServiceImpl.java new file mode 100644 index 0000000..5341e81 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpAutoReplyServiceImpl.java @@ -0,0 +1,202 @@ +package cn.iocoder.yudao.module.mp.service.message; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ObjUtil; +import cn.iocoder.yudao.framework.common.exception.ErrorCode; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.mp.controller.admin.message.vo.autoreply.MpAutoReplyCreateReqVO; +import cn.iocoder.yudao.module.mp.controller.admin.message.vo.autoreply.MpAutoReplyUpdateReqVO; +import cn.iocoder.yudao.module.mp.controller.admin.message.vo.message.MpMessagePageReqVO; +import cn.iocoder.yudao.module.mp.convert.message.MpAutoReplyConvert; +import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; +import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpAutoReplyDO; +import cn.iocoder.yudao.module.mp.dal.mysql.message.MpAutoReplyMapper; +import cn.iocoder.yudao.module.mp.enums.message.MpAutoReplyTypeEnum; +import cn.iocoder.yudao.module.mp.framework.mp.core.util.MpUtils; +import cn.iocoder.yudao.module.mp.service.account.MpAccountService; +import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO; +import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import jakarta.annotation.Resource; +import jakarta.validation.Validator; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.*; + +/** + * 公众号的自动回复 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class MpAutoReplyServiceImpl implements MpAutoReplyService { + + @Resource + private MpMessageService mpMessageService; + @Resource + @Lazy // 延迟加载,避免循环依赖 + private MpAccountService mpAccountService; + + @Resource + private Validator validator; + + @Resource + private MpAutoReplyMapper mpAutoReplyMapper; + + @Override + public PageResult getAutoReplyPage(MpMessagePageReqVO pageVO) { + return mpAutoReplyMapper.selectPage(pageVO); + } + + @Override + public MpAutoReplyDO getAutoReply(Long id) { + return mpAutoReplyMapper.selectById(id); + } + + @Override + public Long createAutoReply(MpAutoReplyCreateReqVO createReqVO) { + // 第一步,校验数据 + if (createReqVO.getResponseMessageType() != null) { + MpUtils.validateMessage(validator, createReqVO.getResponseMessageType(), createReqVO); + } + validateAutoReplyConflict(null, createReqVO.getAccountId(), createReqVO.getType(), + createReqVO.getRequestKeyword(), createReqVO.getRequestMessageType()); + + // 第二步,插入数据 + MpAccountDO account = mpAccountService.getRequiredAccount(createReqVO.getAccountId()); + MpAutoReplyDO autoReply = MpAutoReplyConvert.INSTANCE.convert(createReqVO) + .setAppId(account.getAppId()); + mpAutoReplyMapper.insert(autoReply); + return autoReply.getId(); + } + + @Override + public void updateAutoReply(MpAutoReplyUpdateReqVO updateReqVO) { + // 第一步,校验数据 + if (updateReqVO.getResponseMessageType() != null) { + MpUtils.validateMessage(validator, updateReqVO.getResponseMessageType(), updateReqVO); + } + MpAutoReplyDO autoReply = validateAutoReplyExists(updateReqVO.getId()); + validateAutoReplyConflict(updateReqVO.getId(), autoReply.getAccountId(), updateReqVO.getType(), + updateReqVO.getRequestKeyword(), updateReqVO.getRequestMessageType()); + + // 第二步,更新数据 + MpAutoReplyDO updateObj = MpAutoReplyConvert.INSTANCE.convert(updateReqVO) + .setAccountId(null).setAppId(null); // 避免前端传递,更新着两个字段 + mpAutoReplyMapper.updateById(updateObj); + } + + /** + * 校验自动回复是否冲突 + * + * 不同的 type,会有不同的逻辑: + * 1. type = SUBSCRIBE 时,不允许有其他的自动回复 + * 2. type = MESSAGE 时,校验 requestMessageType 已经存在自动回复 + * 3. type = KEYWORD 时,校验 keyword 已经存在自动回复 + * + * @param id 自动回复编号 + * @param accountId 公众号账号的编号 + * @param type 类型 + * @param requestKeyword 请求关键词 + * @param requestMessageType 请求消息类型 + */ + private void validateAutoReplyConflict(Long id, Long accountId, Integer type, + String requestKeyword, String requestMessageType) { + // 获得已经存在的自动回复 + MpAutoReplyDO autoReply = null; + ErrorCode errorCode = null; + if (MpAutoReplyTypeEnum.SUBSCRIBE.getType().equals(type)) { + autoReply = mpAutoReplyMapper.selectByAccountIdAndSubscribe(accountId); + errorCode = AUTO_REPLY_ADD_SUBSCRIBE_FAIL_EXISTS; + } else if (MpAutoReplyTypeEnum.MESSAGE.getType().equals(type)) { + autoReply = mpAutoReplyMapper.selectByAccountIdAndMessage(accountId, requestMessageType); + errorCode = AUTO_REPLY_ADD_MESSAGE_FAIL_EXISTS; + } else if (MpAutoReplyTypeEnum.KEYWORD.getType().equals(type)) { + autoReply = mpAutoReplyMapper.selectByAccountIdAndKeyword(accountId, requestKeyword); + errorCode = AUTO_REPLY_ADD_KEYWORD_FAIL_EXISTS; + } + if (autoReply == null) { + return; + } + + // 存在冲突,抛出业务异常 + if (id == null // 情况一,新增(id == null),存在记录,说明冲突 + || ObjUtil.notEqual(id, autoReply.getId())) { // 情况二,修改(id != null),id 不匹配,说明冲突 + throw exception(errorCode); + } + } + + @Override + public void deleteAutoReply(Long id) { + // 校验粉丝存在 + validateAutoReplyExists(id); + + // 删除自动回复 + mpAutoReplyMapper.deleteById(id); + } + + private MpAutoReplyDO validateAutoReplyExists(Long id) { + MpAutoReplyDO autoReply = mpAutoReplyMapper.selectById(id); + if (autoReply == null) { + throw exception(AUTO_REPLY_NOT_EXISTS); + } + return autoReply; + } + + @Override + public WxMpXmlOutMessage replyForMessage(String appId, WxMpXmlMessage wxMessage) { + // 第一步,匹配自动回复 + List replies = null; + // 1.1 关键字 + if (wxMessage.getMsgType().equals(WxConsts.XmlMsgType.TEXT)) { + // 完全匹配 + replies = mpAutoReplyMapper.selectListByAppIdAndKeywordAll(appId, wxMessage.getContent()); + if (CollUtil.isEmpty(replies)) { + // 模糊匹配 + replies = mpAutoReplyMapper.selectListByAppIdAndKeywordLike(appId, wxMessage.getContent()); + } + } + // 1.2 消息类型 + if (CollUtil.isEmpty(replies)) { + replies = mpAutoReplyMapper.selectListByAppIdAndMessage(appId, wxMessage.getMsgType()); + } + if (CollUtil.isEmpty(replies)) { + return null; + } + MpAutoReplyDO reply = CollUtil.getFirst(replies); + + // 第二步,基于自动回复,创建消息 + MpMessageSendOutReqBO sendReqBO = MpAutoReplyConvert.INSTANCE.convert(wxMessage.getFromUser(), reply); + return mpMessageService.sendOutMessage(sendReqBO); + } + + @Override + public WxMpXmlOutMessage replyForSubscribe(String appId, WxMpXmlMessage wxMessage) { + // 第一步,匹配自动回复 + List replies = mpAutoReplyMapper.selectListByAppIdAndSubscribe(appId); + MpAutoReplyDO reply = CollUtil.isNotEmpty(replies) ? CollUtil.getFirst(replies) + : buildDefaultSubscribeAutoReply(appId); // 如果不存在,提供一个默认末班 + + // 第二步,基于自动回复,创建消息 + MpMessageSendOutReqBO sendReqBO = MpAutoReplyConvert.INSTANCE.convert(wxMessage.getFromUser(), reply); + return mpMessageService.sendOutMessage(sendReqBO); + } + + private MpAutoReplyDO buildDefaultSubscribeAutoReply(String appId) { + MpAccountDO account = mpAccountService.getAccountFromCache(appId); + Assert.notNull(account, "公众号账号({}) 不存在", appId); + // 构建默认的【关注】自动回复 + return new MpAutoReplyDO().setAppId(appId).setAccountId(account.getId()) + .setType(MpAutoReplyTypeEnum.SUBSCRIBE.getType()) + .setResponseMessageType(WxConsts.XmlMsgType.TEXT).setResponseContent("感谢关注"); + } + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageService.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageService.java new file mode 100644 index 0000000..4f77cbe --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageService.java @@ -0,0 +1,59 @@ +package cn.iocoder.yudao.module.mp.service.message; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.mp.controller.admin.message.vo.message.MpMessagePageReqVO; +import cn.iocoder.yudao.module.mp.controller.admin.message.vo.message.MpMessageSendReqVO; +import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO; +import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; + +import jakarta.validation.Valid; + +/** + * 公众号消息 Service 接口 + * + * @author 芋道源码 + */ +public interface MpMessageService { + + /** + * 获得公众号消息分页 + * + * @param pageReqVO 分页查询 + * @return 公众号消息分页 + */ + PageResult getMessagePage(MpMessagePageReqVO pageReqVO); + + /** + * 从公众号,接收到粉丝消息 + * + * @param appId 微信公众号 appId + * @param wxMessage 消息 + */ + void receiveMessage(WxMpService weixinService, String appId, WxMpXmlMessage wxMessage); + + /** + * 使用公众号,给粉丝回复消息 + * + * 例如说:自动回复、客服消息、菜单回复消息等场景 + * + * 注意,该方法只是返回 WxMpXmlOutMessage 对象,不会真的发送消息 + * + * @param sendReqBO 消息内容 + * @return 微信回复消息 XML + */ + WxMpXmlOutMessage sendOutMessage(@Valid MpMessageSendOutReqBO sendReqBO); + + /** + * 使用公众号,给粉丝发送【客服】消息 + * + * 注意,该方法会真实发送消息 + * + * @param sendReqVO 消息内容 + * @return 消息 + */ + MpMessageDO sendKefuMessage(MpMessageSendReqVO sendReqVO); + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageServiceImpl.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageServiceImpl.java new file mode 100644 index 0000000..4a511c8 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageServiceImpl.java @@ -0,0 +1,172 @@ +package cn.iocoder.yudao.module.mp.service.message; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.thread.ThreadUtil; +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.mp.controller.admin.message.vo.message.MpMessagePageReqVO; +import cn.iocoder.yudao.module.mp.controller.admin.message.vo.message.MpMessageSendReqVO; +import cn.iocoder.yudao.module.mp.convert.message.MpMessageConvert; +import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; +import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO; +import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO; +import cn.iocoder.yudao.module.mp.dal.mysql.message.MpMessageMapper; +import cn.iocoder.yudao.module.mp.enums.message.MpMessageSendFromEnum; +import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory; +import cn.iocoder.yudao.module.mp.framework.mp.core.util.MpUtils; +import cn.iocoder.yudao.module.mp.service.account.MpAccountService; +import cn.iocoder.yudao.module.mp.service.material.MpMaterialService; +import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO; +import cn.iocoder.yudao.module.mp.service.user.MpUserService; +import jakarta.annotation.Resource; +import jakarta.validation.Validator; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.kefu.WxMpKefuMessage; +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.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.util.concurrent.TimeUnit; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.MESSAGE_SEND_FAIL; + +/** + * 粉丝消息 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +@Slf4j +public class MpMessageServiceImpl implements MpMessageService { + + @Resource + @Lazy // 延迟加载,避免循环依赖 + private MpAccountService mpAccountService; + @Resource + private MpUserService mpUserService; + @Resource + private MpMaterialService mpMaterialService; + + @Resource + private MpMessageMapper mpMessageMapper; + + @Resource + @Lazy // 延迟加载,解决循环依赖的问题 + private MpServiceFactory mpServiceFactory; + + @Resource + private Validator validator; + + @Override + public PageResult getMessagePage(MpMessagePageReqVO pageReqVO) { + return mpMessageMapper.selectPage(pageReqVO); + } + + @Override + @SneakyThrows + public void receiveMessage(WxMpService weixinService, String appId, WxMpXmlMessage wxMessage) { + // 获得关联信息 + MpAccountDO account = mpAccountService.getAccountFromCache(appId); + Assert.notNull(account, "公众号账号({}) 不存在", appId); + + // 获取用户 + MpUserDO user = mpUserService.getUser(appId, wxMessage.getFromUser()); + if (user == null) { + // 特殊情况:因为 receiveMessage 是异步记录,可能 SubscribeHandler 还没存储好 User,此时 sleep 轮询 + for (int i = 0; i < 3; i++) { + ThreadUtil.sleep(5, TimeUnit.SECONDS); + user = mpUserService.getUser(appId, wxMessage.getFromUser()); + if (user != null) { + break; + } + log.warn("[receiveMessage][粉丝({}/{}) 不存在,第 {} 次重试失败]", appId, wxMessage.getFromUser(), i + 1); + } + } + // 特殊情况:可能 SubscribeHandler 没处理正确(例如说发生异常),则主动创建 + if (user == null) { + log.warn("[receiveMessage][粉丝({}/{}) 不存在,主动创建]", appId, wxMessage.getFromUser()); + WxMpUser wxMpUser = weixinService.getUserService().userInfo(wxMessage.getFromUser()); + user = mpUserService.saveUser(appId, wxMpUser); + } + Assert.notNull(user, "公众号粉丝({}/{}) 不存在", appId, wxMessage.getFromUser()); + + // 记录消息 + MpMessageDO message = MpMessageConvert.INSTANCE.convert(wxMessage, account, user) + .setSendFrom(MpMessageSendFromEnum.USER_TO_MP.getFrom()); + downloadMessageMedia(message); + mpMessageMapper.insert(message); + } + + @Override + public WxMpXmlOutMessage sendOutMessage(MpMessageSendOutReqBO sendReqBO) { + // 校验消息格式 + MpUtils.validateMessage(validator, sendReqBO.getType(), sendReqBO); + + // 获得关联信息 + MpAccountDO account = mpAccountService.getAccountFromCache(sendReqBO.getAppId()); + Assert.notNull(account, "公众号账号({}) 不存在", sendReqBO.getAppId()); + MpUserDO user = mpUserService.getUser(sendReqBO.getAppId(), sendReqBO.getOpenid()); + Assert.notNull(user, "公众号粉丝({}/{}) 不存在", sendReqBO.getAppId(), sendReqBO.getOpenid()); + + // 记录消息 + MpMessageDO message = MpMessageConvert.INSTANCE.convert(sendReqBO, account, user). + setSendFrom(MpMessageSendFromEnum.MP_TO_USER.getFrom()); + downloadMessageMedia(message); + mpMessageMapper.insert(message); + + // 转换返回 WxMpXmlOutMessage 对象 + return MpMessageConvert.INSTANCE.convert02(message, account); + } + + @Override + public MpMessageDO sendKefuMessage(MpMessageSendReqVO sendReqVO) { + // 校验消息格式 + MpUtils.validateMessage(validator, sendReqVO.getType(), sendReqVO); + + // 获得关联信息 + MpUserDO user = mpUserService.getRequiredUser(sendReqVO.getUserId()); + MpAccountDO account = mpAccountService.getRequiredAccount(user.getAccountId()); + + // 发送客服消息 + WxMpKefuMessage wxMessage = MpMessageConvert.INSTANCE.convert(sendReqVO, user); + WxMpService mpService = mpServiceFactory.getRequiredMpService(user.getAppId()); + try { + mpService.getKefuService().sendKefuMessageWithResponse(wxMessage); + } catch (WxErrorException e) { + throw exception(MESSAGE_SEND_FAIL, e.getError().getErrorMsg()); + } + + // 记录消息 + MpMessageDO message = MpMessageConvert.INSTANCE.convert(wxMessage, account, user) + .setSendFrom(MpMessageSendFromEnum.MP_TO_USER.getFrom()); + downloadMessageMedia(message); + mpMessageMapper.insert(message); + return message; + } + + /** + * 下载消息使用到的媒体文件,并上传到文件服务 + * + * @param message 消息 + */ + private void downloadMessageMedia(MpMessageDO message) { + if (StrUtil.isNotEmpty(message.getMediaId())) { + message.setMediaUrl(mpMaterialService.downloadMaterialUrl(message.getAccountId(), + message.getMediaId(), MpUtils.getMediaFileType(message.getType()))); + } + if (StrUtil.isNotEmpty(message.getThumbMediaId())) { + message.setThumbMediaUrl(mpMaterialService.downloadMaterialUrl(message.getAccountId(), + message.getThumbMediaId(), WxConsts.MediaFileType.THUMB)); + } + } + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/message/bo/MpMessageSendOutReqBO.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/message/bo/MpMessageSendOutReqBO.java new file mode 100644 index 0000000..2a53649 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/message/bo/MpMessageSendOutReqBO.java @@ -0,0 +1,110 @@ +package cn.iocoder.yudao.module.mp.service.message.bo; + +import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO; +import cn.iocoder.yudao.module.mp.framework.mp.core.util.MpUtils.*; +import lombok.Data; +import me.chanjar.weixin.common.api.WxConsts; +import org.hibernate.validator.constraints.URL; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import java.util.List; + +/** + * 公众号消息发送 Request BO + * + * 为什么要有该 BO 呢?在自动回复、客服消息、菜单回复消息等场景,都涉及到 MP 给粉丝发送消息,所以使用该 BO 统一承接 + * + * @author 芋道源码 + */ +@Data +public class MpMessageSendOutReqBO { + + /** + * 公众号 appId + */ + @NotEmpty(message = "公众号 appId 不能为空") + private String appId; + /** + * 公众号粉丝 openid + */ + @NotEmpty(message = "公众号粉丝 openid 不能为空") + private String openid; + + // ========== 消息内容 ========== + /** + * 消息类型 + * + * 枚举 {@link WxConsts.XmlMsgType} 中的 TEXT、IMAGE、VOICE、VIDEO、NEWS、MUSIC + */ + @NotEmpty(message = "消息类型不能为空") + public String type; + + /** + * 消息内容 + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 TEXT + */ + @NotEmpty(message = "消息内容不能为空", groups = TextMessageGroup.class) + private String content; + + /** + * 媒体 id + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 IMAGE、VOICE、VIDEO + */ + @NotEmpty(message = "消息 mediaId 不能为空", groups = {ImageMessageGroup.class, VoiceMessageGroup.class, VideoMessageGroup.class}) + private String mediaId; + + /** + * 缩略图的媒体 id + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO、MUSIC + */ + @NotEmpty(message = "消息 thumbMediaId 不能为空", groups = {MusicMessageGroup.class}) + private String thumbMediaId; + + /** + * 标题 + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO + */ + @NotEmpty(message = "消息标题不能为空", groups = VideoMessageGroup.class) + private String title; + /** + * 描述 + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO + */ + @NotEmpty(message = "消息描述不能为空", groups = VideoMessageGroup.class) + private String description; + + /** + * 图文消息 + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS + */ + @Valid + @NotNull(message = "图文消息不能为空", groups = NewsMessageGroup.class) + private List articles; + + /** + * 音乐链接 + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC + */ + @NotEmpty(message = "音乐链接不能为空", groups = MusicMessageGroup.class) + @URL(message = "高质量音乐链接格式不正确", groups = MusicMessageGroup.class) + private String musicUrl; + + /** + * 高质量音乐链接 + * + * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC + */ + @NotEmpty(message = "高质量音乐链接不能为空", groups = MusicMessageGroup.class) + @URL(message = "高质量音乐链接格式不正确", groups = MusicMessageGroup.class) + private String hqMusicUrl; + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/statistics/MpStatisticsService.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/statistics/MpStatisticsService.java new file mode 100644 index 0000000..08e5c35 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/statistics/MpStatisticsService.java @@ -0,0 +1,54 @@ +package cn.iocoder.yudao.module.mp.service.statistics; + +import me.chanjar.weixin.mp.bean.datacube.WxDataCubeInterfaceResult; +import me.chanjar.weixin.mp.bean.datacube.WxDataCubeMsgResult; +import me.chanjar.weixin.mp.bean.datacube.WxDataCubeUserCumulate; +import me.chanjar.weixin.mp.bean.datacube.WxDataCubeUserSummary; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 公众号统计 Service 接口 + * + * @author 芋道源码 + */ +public interface MpStatisticsService { + + /** + * 获取粉丝增减数据 + * + * @param accountId 公众号账号编号 + * @param date 时间区间 + * @return 粉丝增减数据 + */ + List getUserSummary(Long accountId, LocalDateTime[] date); + + /** + * 获取粉丝累计数据 + * + * @param accountId 公众号账号编号 + * @param date 时间区间 + * @return 粉丝累计数据 + */ + List getUserCumulate(Long accountId, LocalDateTime[] date); + + /** + * 获取消息发送概况数据 + * + * @param accountId 公众号账号编号 + * @param date 时间区间 + * @return 消息发送概况数据 + */ + List getUpstreamMessage(Long accountId, LocalDateTime[] date); + + /** + * 获取接口分析数据 + * + * @param accountId 公众号账号编号 + * @param date 时间区间 + * @return 接口分析数据 + */ + List getInterfaceSummary(Long accountId, LocalDateTime[] date); + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/statistics/MpStatisticsServiceImpl.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/statistics/MpStatisticsServiceImpl.java new file mode 100644 index 0000000..5ee2073 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/statistics/MpStatisticsServiceImpl.java @@ -0,0 +1,77 @@ +package cn.iocoder.yudao.module.mp.service.statistics; + +import cn.hutool.core.date.DateUtil; +import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.datacube.WxDataCubeInterfaceResult; +import me.chanjar.weixin.mp.bean.datacube.WxDataCubeMsgResult; +import me.chanjar.weixin.mp.bean.datacube.WxDataCubeUserCumulate; +import me.chanjar.weixin.mp.bean.datacube.WxDataCubeUserSummary; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; + +import jakarta.annotation.Resource; +import java.time.LocalDateTime; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.*; + +/** + * 公众号统计 Service 实现类 + * + * @author 芋道源码 + */ +@Service +public class MpStatisticsServiceImpl implements MpStatisticsService { + + @Resource + @Lazy // 延迟加载,解决循环依赖的问题 + private MpServiceFactory mpServiceFactory; + + @Override + public List getUserSummary(Long accountId, LocalDateTime[] date) { + WxMpService mpService = mpServiceFactory.getRequiredMpService(accountId); + try { + return mpService.getDataCubeService().getUserSummary( + DateUtil.date(date[0]), DateUtil.date(date[1])); + } catch (WxErrorException e) { + throw exception(STATISTICS_GET_USER_SUMMARY_FAIL, e.getError().getErrorMsg()); + } + } + + @Override + public List getUserCumulate(Long accountId, LocalDateTime[] date) { + WxMpService mpService = mpServiceFactory.getRequiredMpService(accountId); + try { + return mpService.getDataCubeService().getUserCumulate( + DateUtil.date(date[0]), DateUtil.date(date[1])); + } catch (WxErrorException e) { + throw exception(STATISTICS_GET_USER_CUMULATE_FAIL, e.getError().getErrorMsg()); + } + } + + @Override + public List getUpstreamMessage(Long accountId, LocalDateTime[] date) { + WxMpService mpService = mpServiceFactory.getRequiredMpService(accountId); + try { + return mpService.getDataCubeService().getUpstreamMsg( + DateUtil.date(date[0]), DateUtil.date(date[1])); + } catch (WxErrorException e) { + throw exception(STATISTICS_GET_UPSTREAM_MESSAGE_FAIL, e.getError().getErrorMsg()); + } + } + + @Override + public List getInterfaceSummary(Long accountId, LocalDateTime[] date) { + WxMpService mpService = mpServiceFactory.getRequiredMpService(accountId); + try { + return mpService.getDataCubeService().getInterfaceSummary( + DateUtil.date(date[0]), DateUtil.date(date[1])); + } catch (WxErrorException e) { + throw exception(STATISTICS_GET_INTERFACE_SUMMARY_FAIL, e.getError().getErrorMsg()); + } + } + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/tag/MpTagService.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/tag/MpTagService.java new file mode 100644 index 0000000..eb32486 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/tag/MpTagService.java @@ -0,0 +1,65 @@ +package cn.iocoder.yudao.module.mp.service.tag; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.mp.controller.admin.tag.vo.MpTagCreateReqVO; +import cn.iocoder.yudao.module.mp.controller.admin.tag.vo.MpTagPageReqVO; +import cn.iocoder.yudao.module.mp.controller.admin.tag.vo.MpTagUpdateReqVO; +import cn.iocoder.yudao.module.mp.dal.dataobject.tag.MpTagDO; + +import jakarta.validation.Valid; +import java.util.List; + +/** + * 公众号标签 Service 接口 + * + * @author fengdan + */ +public interface MpTagService { + + /** + * 创建公众号标签 + * + * @param createReqVO 创建标签信息 + * @return 标签编号 + */ + Long createTag(@Valid MpTagCreateReqVO createReqVO); + + /** + * 更新公众号标签 + * + * @param updateReqVO 更新标签信息 + */ + void updateTag(@Valid MpTagUpdateReqVO updateReqVO); + + /** + * 删除公众号标签 + * + * @param id 编号 + */ + void deleteTag(Long id); + + /** + * 获得公众号标签分页 + * + * @param pageReqVO 分页查询 + * @return 公众号标签分页 + */ + PageResult getTagPage(MpTagPageReqVO pageReqVO); + + /** + * 获得公众号标签详情 + * @param id id查询 + * @return 公众号标签详情 + */ + MpTagDO get(Long id); + + List getTagList(); + + /** + * 同步公众号标签 + * + * @param accountId 公众号账号的编号 + */ + void syncTag(Long accountId); + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/tag/MpTagServiceImpl.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/tag/MpTagServiceImpl.java new file mode 100644 index 0000000..f48c1ab --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/tag/MpTagServiceImpl.java @@ -0,0 +1,164 @@ +package cn.iocoder.yudao.module.mp.service.tag; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.mp.controller.admin.tag.vo.MpTagCreateReqVO; +import cn.iocoder.yudao.module.mp.controller.admin.tag.vo.MpTagPageReqVO; +import cn.iocoder.yudao.module.mp.controller.admin.tag.vo.MpTagUpdateReqVO; +import cn.iocoder.yudao.module.mp.convert.tag.MpTagConvert; +import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; +import cn.iocoder.yudao.module.mp.dal.dataobject.tag.MpTagDO; +import cn.iocoder.yudao.module.mp.dal.mysql.tag.MpTagMapper; +import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory; +import cn.iocoder.yudao.module.mp.service.account.MpAccountService; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.tag.WxUserTag; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import jakarta.annotation.Resource; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; +import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.*; + +/** + * 公众号标签 Service 实现类 + * + * @author fengdan + */ +@Slf4j +@Service +@Validated +public class MpTagServiceImpl implements MpTagService { + + @Resource + private MpTagMapper mpTagMapper; + + @Resource + private MpAccountService mpAccountService; + + @Resource + @Lazy // 延迟加载,为了解决延迟加载 + private MpServiceFactory mpServiceFactory; + + @Override + public Long createTag(MpTagCreateReqVO createReqVO) { + // 获得公众号账号 + MpAccountDO account = mpAccountService.getRequiredAccount(createReqVO.getAccountId()); + + // 第一步,新增标签到公众号平台。标签名的唯一,交给公众号平台 + WxMpService mpService = mpServiceFactory.getRequiredMpService(createReqVO.getAccountId()); + WxUserTag wxTag; + try { + wxTag = mpService.getUserTagService().tagCreate(createReqVO.getName()); + } catch (WxErrorException e) { + throw exception(TAG_CREATE_FAIL, e.getError().getErrorMsg()); + } + + // 第二步,新增标签到数据库 + MpTagDO tag = MpTagConvert.INSTANCE.convert(wxTag, account); + mpTagMapper.insert(tag); + return tag.getId(); + } + + @Override + public void updateTag(MpTagUpdateReqVO updateReqVO) { + // 校验标签存在 + MpTagDO tag = validateTagExists(updateReqVO.getId()); + + // 第一步,更新标签到公众号平台。标签名的唯一,交给公众号平台 + WxMpService mpService = mpServiceFactory.getRequiredMpService(tag.getAccountId()); + try { + mpService.getUserTagService().tagUpdate(tag.getTagId(), updateReqVO.getName()); + } catch (WxErrorException e) { + throw exception(TAG_UPDATE_FAIL, e.getError().getErrorMsg()); + } + + // 第二步,更新标签到数据库 + mpTagMapper.updateById(new MpTagDO().setId(tag.getId()).setName(updateReqVO.getName())); + } + + @Override + public void deleteTag(Long id) { + // 校验标签存在 + MpTagDO tag = validateTagExists(id); + + // 第一步,删除标签到公众号平台。 + WxMpService mpService = mpServiceFactory.getRequiredMpService(tag.getAccountId()); + try { + mpService.getUserTagService().tagDelete(tag.getTagId()); + } catch (WxErrorException e) { + throw exception(TAG_DELETE_FAIL, e.getError().getErrorMsg()); + } + + // 第二步,删除标签到数据库 + mpTagMapper.deleteById(tag.getId()); + } + + private MpTagDO validateTagExists(Long id) { + MpTagDO tag = mpTagMapper.selectById(id); + if (tag == null) { + throw exception(TAG_NOT_EXISTS); + } + return tag; + } + + @Override + public PageResult getTagPage(MpTagPageReqVO pageReqVO) { + return mpTagMapper.selectPage(pageReqVO); + } + + @Override + public MpTagDO get(Long id) { + return mpTagMapper.selectById(id); + } + + @Override + public List getTagList() { + return mpTagMapper.selectList(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void syncTag(Long accountId) { + MpAccountDO account = mpAccountService.getRequiredAccount(accountId); + + // 第一步,从公众号平台获取最新的标签列表 + WxMpService mpService = mpServiceFactory.getRequiredMpService(accountId); + List wxTags; + try { + wxTags = mpService.getUserTagService().tagGet(); + } catch (WxErrorException e) { + throw exception(TAG_GET_FAIL, e.getError().getErrorMsg()); + } + + // 第二步,合并更新回自己的数据库;由于标签只有 100 个,所以直接 for 循环操作 + Map tagMap = convertMap(mpTagMapper.selectListByAccountId(accountId), + MpTagDO::getTagId); + wxTags.forEach(wxTag -> { + MpTagDO tag = tagMap.remove(wxTag.getId()); + // 情况一,不存在,新增 + if (tag == null) { + tag = MpTagConvert.INSTANCE.convert(wxTag, account); + mpTagMapper.insert(tag); + return; + } + // 情况二,存在,则更新 + mpTagMapper.updateById(new MpTagDO().setId(tag.getId()) + .setName(wxTag.getName()).setCount(wxTag.getCount())); + }); + // 情况三,部分标签已经不存在了,删除 + if (CollUtil.isNotEmpty(tagMap)) { + mpTagMapper.deleteBatchIds(convertList(tagMap.values(), MpTagDO::getId)); + } + } + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/user/MpUserService.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/user/MpUserService.java new file mode 100644 index 0000000..bdefc0e --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/user/MpUserService.java @@ -0,0 +1,102 @@ +package cn.iocoder.yudao.module.mp.service.user; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.mp.controller.admin.user.vo.MpUserPageReqVO; +import cn.iocoder.yudao.module.mp.controller.admin.user.vo.MpUserUpdateReqVO; +import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO; +import me.chanjar.weixin.mp.bean.result.WxMpUser; + +import java.util.Collection; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.USER_NOT_EXISTS; + +/** + * 公众号粉丝 Service 接口 + * + * @author 芋道源码 + */ +public interface MpUserService { + + /** + * 获得公众号粉丝 + * + * @param id 编号 + * @return 公众号粉丝 + */ + MpUserDO getUser(Long id); + + /** + * 使用 appId + openId,获得公众号粉丝 + * + * @param appId 公众号 appId + * @param openId 公众号 openId + * @return 公众号粉丝 + */ + MpUserDO getUser(String appId, String openId); + + /** + * 获得公众号粉丝 + * + * @param id 编号 + * @return 公众号粉丝 + */ + default MpUserDO getRequiredUser(Long id) { + MpUserDO user = getUser(id); + if (user == null) { + throw exception(USER_NOT_EXISTS); + } + return user; + } + + /** + * 获得公众号粉丝列表 + * + * @param ids 编号 + * @return 公众号粉丝列表 + */ + List getUserList(Collection ids); + + /** + * 获得公众号粉丝分页 + * + * @param pageReqVO 分页查询 + * @return 公众号粉丝分页 + */ + PageResult getUserPage(MpUserPageReqVO pageReqVO); + + /** + * 保存公众号粉丝 + * + * 新增或更新,根据是否存在数据库中 + * + * @param appId 公众号 appId + * @param wxMpUser 公众号粉丝的信息 + * @return 公众号粉丝 + */ + MpUserDO saveUser(String appId, WxMpUser wxMpUser); + + /** + * 同步一个公众号粉丝 + * + * @param accountId 公众号账号的编号 + */ + void syncUser(Long accountId); + + /** + * 更新公众号粉丝,取消关注 + * + * @param appId 公众号 appId + * @param openId 公众号粉丝的 openid + */ + void updateUserUnsubscribe(String appId, String openId); + + /** + * 更新公众号粉丝 + * + * @param updateReqVO 更新信息 + */ + void updateUser(MpUserUpdateReqVO updateReqVO); + +} diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/user/MpUserServiceImpl.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/user/MpUserServiceImpl.java new file mode 100644 index 0000000..12dd009 --- /dev/null +++ b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/user/MpUserServiceImpl.java @@ -0,0 +1,215 @@ +package cn.iocoder.yudao.module.mp.service.user; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; +import cn.iocoder.yudao.module.mp.controller.admin.user.vo.MpUserPageReqVO; +import cn.iocoder.yudao.module.mp.controller.admin.user.vo.MpUserUpdateReqVO; +import cn.iocoder.yudao.module.mp.convert.user.MpUserConvert; +import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; +import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO; +import cn.iocoder.yudao.module.mp.dal.mysql.user.MpUserMapper; +import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory; +import cn.iocoder.yudao.module.mp.service.account.MpAccountService; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.result.WxMpUser; +import me.chanjar.weixin.mp.bean.result.WxMpUserList; +import org.springframework.context.annotation.Lazy; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import jakarta.annotation.Resource; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.USER_NOT_EXISTS; +import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.USER_UPDATE_TAG_FAIL; + +/** + * 微信公众号粉丝 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +@Slf4j +public class MpUserServiceImpl implements MpUserService { + + @Resource + @Lazy // 延迟加载,解决循环依赖的问题 + private MpAccountService mpAccountService; + + @Resource + @Lazy // 延迟加载,解决循环依赖的问题 + private MpServiceFactory mpServiceFactory; + + @Resource + private MpUserMapper mpUserMapper; + + @Override + public MpUserDO getUser(Long id) { + return mpUserMapper.selectById(id); + } + + @Override + public MpUserDO getUser(String appId, String openId) { + return mpUserMapper.selectByAppIdAndOpenid(appId, openId); + } + + @Override + public List getUserList(Collection ids) { + return mpUserMapper.selectBatchIds(ids); + } + + @Override + public PageResult getUserPage(MpUserPageReqVO pageReqVO) { + return mpUserMapper.selectPage(pageReqVO); + } + + @Override + public MpUserDO saveUser(String appId, WxMpUser wxMpUser) { + // 构建保存的 MpUserDO 对象 + MpAccountDO account = mpAccountService.getAccountFromCache(appId); + MpUserDO user = MpUserConvert.INSTANCE.convert(account, wxMpUser); + + // 根据情况,插入或更新 + MpUserDO dbUser = mpUserMapper.selectByAppIdAndOpenid(appId, wxMpUser.getOpenId()); + if (dbUser == null) { + mpUserMapper.insert(user); + } else { + user.setId(dbUser.getId()); + mpUserMapper.updateById(user); + } + return user; + } + + @Override + @Async + public void syncUser(Long accountId) { + MpAccountDO account = mpAccountService.getRequiredAccount(accountId); + // for 循环,避免递归出意外问题,导致死循环 + String nextOpenid = null; + for (int i = 0; i < Short.MAX_VALUE; i++) { + log.info("[syncUser][第({}) 次加载公众号粉丝列表,nextOpenid({})]", i, nextOpenid); + try { + nextOpenid = syncUser0(account, nextOpenid); + } catch (WxErrorException e) { + log.error("[syncUser][第({}) 次同步粉丝异常]", i, e); + break; + } + // 如果 nextOpenid 为空,表示已经同步完毕 + if (StrUtil.isEmpty(nextOpenid)) { + break; + } + } + } + + private String syncUser0(MpAccountDO account, String nextOpenid) throws WxErrorException { + // 第一步,从公众号流式加载粉丝 + WxMpService mpService = mpServiceFactory.getRequiredMpService(account.getId()); + WxMpUserList wxUserList = mpService.getUserService().userList(nextOpenid); + if (CollUtil.isEmpty(wxUserList.getOpenids())) { + return null; + } + + // 第二步,分批加载粉丝信息 + List> openidsList = CollUtil.split(wxUserList.getOpenids(), 100); + for (List openids : openidsList) { + log.info("[syncUser][批量加载粉丝信息,openids({})]", openids); + List wxUsers = mpService.getUserService().userInfoList(openids); + batchSaveUser(account, wxUsers); + } + + // 返回下一次的 nextOpenId + return wxUserList.getNextOpenid(); + } + + private void batchSaveUser(MpAccountDO account, List wxUsers) { + if (CollUtil.isEmpty(wxUsers)) { + return; + } + // 1. 获得数据库已保存的粉丝列表 + List dbUsers = mpUserMapper.selectListByAppIdAndOpenid(account.getAppId(), + CollectionUtils.convertList(wxUsers, WxMpUser::getOpenId)); + Map openId2Users = CollectionUtils.convertMap(dbUsers, MpUserDO::getOpenid); + + // 2.1 根据情况,插入或更新 + List users = MpUserConvert.INSTANCE.convertList(account, wxUsers); + List newUsers = new ArrayList<>(); + for (MpUserDO user : users) { + MpUserDO dbUser = openId2Users.get(user.getOpenid()); + if (dbUser == null) { // 新增:稍后批量插入 + newUsers.add(user); + } else { // 更新:直接执行更新 + user.setId(dbUser.getId()); + mpUserMapper.updateById(user); + } + } + // 2.2 批量插入 + if (CollUtil.isNotEmpty(newUsers)) { + mpUserMapper.insertBatch(newUsers); + } + } + + @Override + public void updateUserUnsubscribe(String appId, String openid) { + MpUserDO dbUser = mpUserMapper.selectByAppIdAndOpenid(appId, openid); + if (dbUser == null) { + log.error("[updateUserUnsubscribe][微信公众号粉丝 appId({}) openid({}) 不存在]", appId, openid); + return; + } + mpUserMapper.updateById(new MpUserDO().setId(dbUser.getId()).setSubscribeStatus(CommonStatusEnum.DISABLE.getStatus()) + .setUnsubscribeTime(LocalDateTime.now())); + } + + @Override + public void updateUser(MpUserUpdateReqVO updateReqVO) { + // 校验存在 + MpUserDO user = validateUserExists(updateReqVO.getId()); + + // 第一步,更新标签到公众号 + updateUserTag(user.getAppId(), user.getOpenid(), updateReqVO.getTagIds()); + + // 第二步,更新基本信息到数据库 + MpUserDO updateObj = MpUserConvert.INSTANCE.convert(updateReqVO).setId(user.getId()); + mpUserMapper.updateById(updateObj); + } + + private MpUserDO validateUserExists(Long id) { + MpUserDO user = mpUserMapper.selectById(id); + if (user == null) { + throw exception(USER_NOT_EXISTS); + } + return user; + } + + private void updateUserTag(String appId, String openid, List tagIds) { + WxMpService mpService = mpServiceFactory.getRequiredMpService(appId); + try { + // 第一步,先取消原来的标签 + List oldTagIds = mpService.getUserTagService().userTagList(openid); + for (Long tagId : oldTagIds) { + mpService.getUserTagService().batchUntagging(tagId, new String[]{openid}); + } + // 第二步,再设置新的标签 + if (CollUtil.isEmpty(tagIds)) { + return; + } + for (Long tagId: tagIds) { + mpService.getUserTagService().batchTagging(tagId, new String[]{openid}); + } + } catch (WxErrorException e) { + throw exception(USER_UPDATE_TAG_FAIL, e.getError().getErrorMsg()); + } + } + +} diff --git a/yudao-module-mp/yudao-module-mp-api/pom.xml b/yudao-module-mp/yudao-module-mp-api/pom.xml deleted file mode 100644 index 64b6f50..0000000 --- a/yudao-module-mp/yudao-module-mp-api/pom.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - yudao-module-mp - cn.iocoder.boot - ${revision} - - 4.0.0 - yudao-module-mp-api - jar - - ${project.artifactId} - - mp 模块 API,暴露给其它模块调用 - - - - - cn.iocoder.boot - yudao-common - - - - diff --git a/yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/ErrorCodeConstants.java b/yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/ErrorCodeConstants.java deleted file mode 100644 index 34c94fd..0000000 --- a/yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/ErrorCodeConstants.java +++ /dev/null @@ -1,64 +0,0 @@ -package cn.iocoder.yudao.module.mp.enums; - -import cn.iocoder.yudao.framework.common.exception.ErrorCode; - -/** - * Mp 错误码枚举类 - * - * mp 系统,使用 1-006-000-000 段 - */ -public interface ErrorCodeConstants { - - // ========== 公众号账号 1-006-000-000 ============ - ErrorCode ACCOUNT_NOT_EXISTS = new ErrorCode(1_006_000_000, "公众号账号不存在"); - ErrorCode ACCOUNT_GENERATE_QR_CODE_FAIL = new ErrorCode(1_006_000_001, "生成公众号二维码失败,原因:{}"); - ErrorCode ACCOUNT_CLEAR_QUOTA_FAIL = new ErrorCode(1_006_000_002, "清空公众号的 API 配额失败,原因:{}"); - - // ========== 公众号统计 1-006-001-000 ============ - ErrorCode STATISTICS_GET_USER_SUMMARY_FAIL = new ErrorCode(1_006_001_000, "获取粉丝增减数据失败,原因:{}"); - ErrorCode STATISTICS_GET_USER_CUMULATE_FAIL = new ErrorCode(1_006_001_001, "获得粉丝累计数据失败,原因:{}"); - ErrorCode STATISTICS_GET_UPSTREAM_MESSAGE_FAIL = new ErrorCode(1_006_001_002, "获得消息发送概况数据失败,原因:{}"); - ErrorCode STATISTICS_GET_INTERFACE_SUMMARY_FAIL = new ErrorCode(1_006_001_003, "获得接口分析数据失败,原因:{}"); - - // ========== 公众号标签 1-006-002-000 ============ - ErrorCode TAG_NOT_EXISTS = new ErrorCode(1_006_002_000, "标签不存在"); - ErrorCode TAG_CREATE_FAIL = new ErrorCode(1_006_002_001, "创建标签失败,原因:{}"); - ErrorCode TAG_UPDATE_FAIL = new ErrorCode(1_006_002_002, "更新标签失败,原因:{}"); - ErrorCode TAG_DELETE_FAIL = new ErrorCode(1_006_002_003, "删除标签失败,原因:{}"); - ErrorCode TAG_GET_FAIL = new ErrorCode(1_006_002_004, "获得标签失败,原因:{}"); - - // ========== 公众号粉丝 1-006-003-000 ============ - ErrorCode USER_NOT_EXISTS = new ErrorCode(1_006_003_000, "粉丝不存在"); - ErrorCode USER_UPDATE_TAG_FAIL = new ErrorCode(1_006_003_001, "更新粉丝标签失败,原因:{}"); - - // ========== 公众号素材 1-006-004-000 ============ - ErrorCode MATERIAL_NOT_EXISTS = new ErrorCode(1_006_004_000, "素材不存在"); - ErrorCode MATERIAL_UPLOAD_FAIL = new ErrorCode(1_006_004_001, "上传素材失败,原因:{}"); - ErrorCode MATERIAL_IMAGE_UPLOAD_FAIL = new ErrorCode(1_006_004_002, "上传图片失败,原因:{}"); - ErrorCode MATERIAL_DELETE_FAIL = new ErrorCode(1_006_004_003, "删除素材失败,原因:{}"); - - // ========== 公众号消息 1-006-005-000 ============ - ErrorCode MESSAGE_SEND_FAIL = new ErrorCode(1_006_005_000, "发送消息失败,原因:{}"); - - // ========== 公众号发布能力 1-006-006-000 ============ - ErrorCode FREE_PUBLISH_LIST_FAIL = new ErrorCode(1_006_006_000, "获得已成功发布列表失败,原因:{}"); - ErrorCode FREE_PUBLISH_SUBMIT_FAIL = new ErrorCode(1_006_006_001, "提交发布失败,原因:{}"); - ErrorCode FREE_PUBLISH_DELETE_FAIL = new ErrorCode(1_006_006_002, "删除发布失败,原因:{}"); - - // ========== 公众号草稿 1-006-007-000 ============ - ErrorCode DRAFT_LIST_FAIL = new ErrorCode(1_006_007_000, "获得草稿列表失败,原因:{}"); - ErrorCode DRAFT_CREATE_FAIL = new ErrorCode(1_006_007_001, "创建草稿失败,原因:{}"); - ErrorCode DRAFT_UPDATE_FAIL = new ErrorCode(1_006_007_002, "更新草稿失败,原因:{}"); - ErrorCode DRAFT_DELETE_FAIL = new ErrorCode(1_006_007_003, "删除草稿失败,原因:{}"); - - // ========== 公众号菜单 1-006-008-000 ============ - ErrorCode MENU_SAVE_FAIL = new ErrorCode(1_006_008_000, "创建菜单失败,原因:{}"); - ErrorCode MENU_DELETE_FAIL = new ErrorCode(1_006_008_001, "删除菜单失败,原因:{}"); - - // ========== 公众号自动回复 1-006-009-000 ============ - ErrorCode AUTO_REPLY_NOT_EXISTS = new ErrorCode(1_006_009_000, "自动回复不存在"); - ErrorCode AUTO_REPLY_ADD_SUBSCRIBE_FAIL_EXISTS = new ErrorCode(1_006_009_001, "操作失败,原因:已存在关注时的回复"); - ErrorCode AUTO_REPLY_ADD_MESSAGE_FAIL_EXISTS = new ErrorCode(1_006_009_002, "操作失败,原因:已存在该消息类型的回复"); - ErrorCode AUTO_REPLY_ADD_KEYWORD_FAIL_EXISTS = new ErrorCode(1_006_009_003, "操作失败,原因:已关在该关键字的回复"); - -} diff --git a/yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpAutoReplyMatchEnum.java b/yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpAutoReplyMatchEnum.java deleted file mode 100644 index a5298db..0000000 --- a/yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpAutoReplyMatchEnum.java +++ /dev/null @@ -1,28 +0,0 @@ -package cn.iocoder.yudao.module.mp.enums.message; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -/** - * 公众号消息自动回复的匹配模式 - * - * @author 芋道源码 - */ -@Getter -@AllArgsConstructor -public enum MpAutoReplyMatchEnum { - - ALL(1, "完全匹配"), - LIKE(2, "半匹配"), - ; - - /** - * 匹配 - */ - private final Integer match; - /** - * 匹配的名字 - */ - private final String name; - -} diff --git a/yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpAutoReplyTypeEnum.java b/yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpAutoReplyTypeEnum.java deleted file mode 100644 index 300f1f2..0000000 --- a/yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpAutoReplyTypeEnum.java +++ /dev/null @@ -1,29 +0,0 @@ -package cn.iocoder.yudao.module.mp.enums.message; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -/** - * 公众号消息自动回复的类型 - * - * @author 芋道源码 - */ -@Getter -@AllArgsConstructor -public enum MpAutoReplyTypeEnum { - - SUBSCRIBE(1, "关注时回复"), - MESSAGE(2, "收到消息回复"), - KEYWORD(3, "关键词回复"), - ; - - /** - * 来源 - */ - private final Integer type; - /** - * 类型的名字 - */ - private final String name; - -} diff --git a/yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpMessageSendFromEnum.java b/yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpMessageSendFromEnum.java deleted file mode 100644 index a2af484..0000000 --- a/yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/message/MpMessageSendFromEnum.java +++ /dev/null @@ -1,28 +0,0 @@ -package cn.iocoder.yudao.module.mp.enums.message; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -/** - * 微信公众号消息的发送来源 - * - * @author 芋道源码 - */ -@Getter -@AllArgsConstructor -public enum MpMessageSendFromEnum { - - USER_TO_MP(1, "粉丝发送给公众号"), - MP_TO_USER(2, "公众号发给粉丝"), - ; - - /** - * 来源 - */ - private final Integer from; - /** - * 来源的名字 - */ - private final String name; - -} diff --git a/yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/package-info.java b/yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/package-info.java deleted file mode 100644 index 5987180..0000000 --- a/yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -/** - * mp 模块,我们放微信微信公众号。 - * 例如说:提供微信公众号的账号、菜单、粉丝、标签、消息、自动回复、素材、模板通知、运营数据等功能 - * - * 1. Controller URL:以 /mp/ 开头,避免和其它 Module 冲突 - * 2. DataObject 表名:以 mp_ 开头,方便在数据库中区分 - */ -package cn.iocoder.yudao.module.mp; diff --git a/yudao-module-mp/yudao-module-mp-biz/pom.xml b/yudao-module-mp/yudao-module-mp-biz/pom.xml deleted file mode 100644 index 5e3fefb..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/pom.xml +++ /dev/null @@ -1,91 +0,0 @@ - - - - yudao-module-mp - cn.iocoder.boot - ${revision} - - 4.0.0 - yudao-module-mp-biz - jar - - ${project.artifactId} - - mp 模块,我们放微信微信公众号。 - 例如说:提供微信公众号的账号、菜单、粉丝、标签、消息、自动回复、素材、模板通知、运营数据等功能 - - - - - cn.iocoder.boot - yudao-module-mp-api - ${revision} - - - cn.iocoder.boot - yudao-module-system-api - ${revision} - - - cn.iocoder.boot - yudao-module-infra-api - ${revision} - - - - - cn.iocoder.boot - yudao-spring-boot-starter-biz-tenant - - - - - cn.iocoder.boot - yudao-spring-boot-starter-security - - - - org.springframework.boot - spring-boot-starter-validation - - - - - cn.iocoder.boot - yudao-spring-boot-starter-mybatis - - - - cn.iocoder.boot - yudao-spring-boot-starter-redis - - - - - cn.iocoder.boot - yudao-spring-boot-starter-mq - - - - - cn.iocoder.boot - yudao-spring-boot-starter-test - test - - - - - cn.iocoder.boot - yudao-spring-boot-starter-excel - - - - - com.github.binarywang - wx-java-mp-spring-boot-starter - - - - diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/MpAccountController.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/MpAccountController.java deleted file mode 100644 index 8a77a28..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/MpAccountController.java +++ /dev/null @@ -1,98 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.account; - -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.mp.controller.admin.account.vo.*; -import cn.iocoder.yudao.module.mp.convert.account.MpAccountConvert; -import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; -import cn.iocoder.yudao.module.mp.service.account.MpAccountService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.tags.Tag; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import jakarta.annotation.Resource; -import jakarta.validation.Valid; -import java.util.List; - -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; - -@Tag(name = "管理后台 - 公众号账号") -@RestController -@RequestMapping("/mp/account") -@Validated -public class MpAccountController { - - @Resource - private MpAccountService mpAccountService; - - @PostMapping("/create") - @Operation(summary = "创建公众号账号") - @PreAuthorize("@ss.hasPermission('mp:account:create')") - public CommonResult createAccount(@Valid @RequestBody MpAccountCreateReqVO createReqVO) { - return success(mpAccountService.createAccount(createReqVO)); - } - - @PutMapping("/update") - @Operation(summary = "更新公众号账号") - @PreAuthorize("@ss.hasPermission('mp:account:update')") - public CommonResult updateAccount(@Valid @RequestBody MpAccountUpdateReqVO updateReqVO) { - mpAccountService.updateAccount(updateReqVO); - return success(true); - } - - @DeleteMapping("/delete") - @Operation(summary = "删除公众号账号") - @Parameter(name = "id", description = "编号", required = true) - @PreAuthorize("@ss.hasPermission('mp:account:delete')") - public CommonResult deleteAccount(@RequestParam("id") Long id) { - mpAccountService.deleteAccount(id); - return success(true); - } - - @GetMapping("/get") - @Operation(summary = "获得公众号账号") - @Parameter(name = "id", description = "编号", required = true, example = "1024") - @PreAuthorize("@ss.hasPermission('mp:account:query')") - public CommonResult getAccount(@RequestParam("id") Long id) { - MpAccountDO wxAccount = mpAccountService.getAccount(id); - return success(MpAccountConvert.INSTANCE.convert(wxAccount)); - } - - @GetMapping("/page") - @Operation(summary = "获得公众号账号分页") - @PreAuthorize("@ss.hasPermission('mp:account:query')") - public CommonResult> getAccountPage(@Valid MpAccountPageReqVO pageVO) { - PageResult pageResult = mpAccountService.getAccountPage(pageVO); - return success(MpAccountConvert.INSTANCE.convertPage(pageResult)); - } - - @GetMapping("/list-all-simple") - @Operation(summary = "获取公众号账号精简信息列表") - @PreAuthorize("@ss.hasPermission('mp:account:query')") - public CommonResult> getSimpleAccounts() { - List list = mpAccountService.getAccountList(); - return success(MpAccountConvert.INSTANCE.convertList02(list)); - } - - @PutMapping("/generate-qr-code") - @Operation(summary = "生成公众号二维码") - @Parameter(name = "id", description = "编号", required = true) - @PreAuthorize("@ss.hasPermission('mp:account:qr-code')") - public CommonResult generateAccountQrCode(@RequestParam("id") Long id) { - mpAccountService.generateAccountQrCode(id); - return success(true); - } - - @PutMapping("/clear-quota") - @Operation(summary = "清空公众号 API 配额") - @Parameter(name = "id", description = "编号", required = true) - @PreAuthorize("@ss.hasPermission('mp:account:clear-quota')") - public CommonResult clearAccountQuota(@RequestParam("id") Long id) { - mpAccountService.clearAccountQuota(id); - return success(true); - } - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountBaseVO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountBaseVO.java deleted file mode 100644 index 92eecf9..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountBaseVO.java +++ /dev/null @@ -1,43 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.account.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import jakarta.validation.constraints.NotEmpty; - -/** - * 公众号账号 Base VO,提供给添加、修改、详细的子 VO 使用 - * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 - * - * @author fengdan - */ -@Data -public class MpAccountBaseVO { - - @Schema(description = "公众号名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道源码") - @NotEmpty(message = "公众号名称不能为空") - private String name; - - @Schema(description = "公众号微信号", requiredMode = Schema.RequiredMode.REQUIRED, example = "yudaoyuanma") - @NotEmpty(message = "公众号微信号不能为空") - private String account; - - @Schema(description = "公众号 appId", requiredMode = Schema.RequiredMode.REQUIRED, example = "wx5b23ba7a5589ecbb") - @NotEmpty(message = "公众号 appId 不能为空") - private String appId; - - @Schema(description = "公众号密钥", requiredMode = Schema.RequiredMode.REQUIRED, example = "3a7b3b20c537e52e74afd395eb85f61f") - @NotEmpty(message = "公众号密钥不能为空") - private String appSecret; - - @Schema(description = "公众号 token", requiredMode = Schema.RequiredMode.REQUIRED, example = "kangdayuzhen") - @NotEmpty(message = "公众号 token 不能为空") - private String token; - - @Schema(description = "加密密钥", example = "gjN+Ksei") - private String aesKey; - - @Schema(description = "备注", example = "请关注芋道源码,学习技术") - private String remark; - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountCreateReqVO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountCreateReqVO.java deleted file mode 100644 index b864f16..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountCreateReqVO.java +++ /dev/null @@ -1,14 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.account.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; - -@Schema(description = "管理后台 - 公众号账号创建 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class MpAccountCreateReqVO extends MpAccountBaseVO { - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountPageReqVO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountPageReqVO.java deleted file mode 100644 index 93093dc..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountPageReqVO.java +++ /dev/null @@ -1,24 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.account.vo; - -import cn.iocoder.yudao.framework.common.pojo.PageParam; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; - -@Schema(description = "管理后台 - 公众号账号分页 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class MpAccountPageReqVO extends PageParam { - - @Schema(name = "公众号名称", description = "模糊匹配") - private String name; - - @Schema(name = "公众号账号", description = "模糊匹配") - private String account; - - @Schema(name = "公众号 appid", description = "模糊匹配") - private String appId; - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountRespVO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountRespVO.java deleted file mode 100644 index 0f7ca7e..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountRespVO.java +++ /dev/null @@ -1,25 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.account.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; - -import java.time.LocalDateTime; - -@Schema(description = "管理后台 - 公众号账号 Response VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class MpAccountRespVO extends MpAccountBaseVO { - - @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - private Long id; - - @Schema(description = "二维码图片URL", example = "https://www.iocoder.cn/1024.png") - private String qrCodeUrl; - - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) - private LocalDateTime createTime; - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountSimpleRespVO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountSimpleRespVO.java deleted file mode 100644 index db4594f..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountSimpleRespVO.java +++ /dev/null @@ -1,16 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.account.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -@Schema(description = "管理后台 - 公众号账号精简信息 Response VO") -@Data -public class MpAccountSimpleRespVO { - - @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - private Long id; - - @Schema(description = "公众号名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道源码") - private String name; - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountUpdateReqVO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountUpdateReqVO.java deleted file mode 100644 index a5bec7c..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountUpdateReqVO.java +++ /dev/null @@ -1,20 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.account.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; - -import jakarta.validation.constraints.NotNull; - -@Schema(description = "管理后台 - 公众号账号更新 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class MpAccountUpdateReqVO extends MpAccountBaseVO { - - @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - @NotNull(message = "编号不能为空") - private Long id; - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/MpMaterialController.http b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/MpMaterialController.http deleted file mode 100644 index c093adf..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/MpMaterialController.http +++ /dev/null @@ -1,5 +0,0 @@ -### 请求 /mp/material/page 接口 => 成功 -GET {{baseUrl}}/mp/material/page?permanent=true&pageNo=1&pageSize=10 -Content-Type: application/json -Authorization: Bearer {{token}} -tenant-id: {{adminTenantId}} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/MpMaterialController.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/MpMaterialController.java deleted file mode 100644 index 9db08bb..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/MpMaterialController.java +++ /dev/null @@ -1,74 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.material; - -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.mp.controller.admin.material.vo.*; -import cn.iocoder.yudao.module.mp.convert.material.MpMaterialConvert; -import cn.iocoder.yudao.module.mp.dal.dataobject.material.MpMaterialDO; -import cn.iocoder.yudao.module.mp.service.material.MpMaterialService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.tags.Tag; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import jakarta.annotation.Resource; -import jakarta.validation.Valid; -import java.io.IOException; - -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; - -@Tag(name = "管理后台 - 公众号素材") -@RestController -@RequestMapping("/mp/material") -@Validated -public class MpMaterialController { - - @Resource - private MpMaterialService mpMaterialService; - - @Operation(summary = "上传临时素材") - @PostMapping("/upload-temporary") - @PreAuthorize("@ss.hasPermission('mp:material:upload-temporary')") - public CommonResult uploadTemporaryMaterial( - @Valid MpMaterialUploadTemporaryReqVO reqVO) throws IOException { - MpMaterialDO material = mpMaterialService.uploadTemporaryMaterial(reqVO); - return success(MpMaterialConvert.INSTANCE.convert(material)); - } - - @Operation(summary = "上传永久素材") - @PostMapping("/upload-permanent") - @PreAuthorize("@ss.hasPermission('mp:material:upload-permanent')") - public CommonResult uploadPermanentMaterial( - @Valid MpMaterialUploadPermanentReqVO reqVO) throws IOException { - MpMaterialDO material = mpMaterialService.uploadPermanentMaterial(reqVO); - return success(MpMaterialConvert.INSTANCE.convert(material)); - } - - @Operation(summary = "删除素材") - @DeleteMapping("/delete-permanent") - @Parameter(name = "id", description = "编号", required = true, example = "1024") - @PreAuthorize("@ss.hasPermission('mp:material:delete')") - public CommonResult deleteMaterial(@RequestParam("id") Long id) { - mpMaterialService.deleteMaterial(id); - return success(true); - } - - @Operation(summary = "上传图文内容中的图片") - @PostMapping("/upload-news-image") - @PreAuthorize("@ss.hasPermission('mp:material:upload-news-image')") - public CommonResult uploadNewsImage(@Valid MpMaterialUploadNewsImageReqVO reqVO) - throws IOException { - return success(mpMaterialService.uploadNewsImage(reqVO)); - } - - @Operation(summary = "获得素材分页") - @GetMapping("/page") - @PreAuthorize("@ss.hasPermission('mp:material:query')") - public CommonResult> getMaterialPage(@Valid MpMaterialPageReqVO pageReqVO) { - PageResult pageResult = mpMaterialService.getMaterialPage(pageReqVO); - return success(MpMaterialConvert.INSTANCE.convertPage(pageResult)); - } - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialPageReqVO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialPageReqVO.java deleted file mode 100644 index 16e6eb5..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialPageReqVO.java +++ /dev/null @@ -1,27 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.material.vo; - -import cn.iocoder.yudao.framework.common.pojo.PageParam; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; - -import jakarta.validation.constraints.NotNull; - -@Schema(description = "管理后台 - 公众号素材的分页 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class MpMaterialPageReqVO extends PageParam { - - @Schema(description = "公众号账号的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") - @NotNull(message = "公众号账号的编号不能为空") - private Long accountId; - - @Schema(description = "是否永久", example = "true") - private Boolean permanent; - - @Schema(description = "文件类型 参见 WxConsts.MediaFileType 枚举", example = "image") - private String type; - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialRespVO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialRespVO.java deleted file mode 100644 index 5aba24e..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialRespVO.java +++ /dev/null @@ -1,47 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.material.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import java.time.LocalDateTime; - -@Schema(description = "管理后台 - 公众号素材 Response VO") -@Data -public class MpMaterialRespVO { - - @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - private Long id; - - @Schema(description = "公众号账号的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - private Long accountId; - @Schema(description = "公众号账号的 appId", requiredMode = Schema.RequiredMode.REQUIRED, example = "wx1234567890") - private String appId; - - @Schema(description = "素材的 media_id", requiredMode = Schema.RequiredMode.REQUIRED, example = "123") - private String mediaId; - - @Schema(description = "文件类型 参见 WxConsts.MediaFileType 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "image") - private String type; - - @Schema(description = "是否永久 true - 永久;false - 临时", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") - private Boolean permanent; - - @Schema(description = "素材的 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.png") - private String url; - - - @Schema(description = "名字", example = "yunai.png") - private String name; - - @Schema(description = "公众号文件 URL 只有【永久素材】使用", example = "https://mmbiz.qpic.cn/xxx.mp3") - private String mpUrl; - - @Schema(description = "视频素材的标题 只有【永久素材】使用", example = "我是标题") - private String title; - @Schema(description = "视频素材的描述 只有【永久素材】使用", example = "我是介绍") - private String introduction; - - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) - private LocalDateTime createTime; - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialUploadNewsImageReqVO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialUploadNewsImageReqVO.java deleted file mode 100644 index 070957f..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialUploadNewsImageReqVO.java +++ /dev/null @@ -1,23 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.material.vo; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import org.springframework.web.multipart.MultipartFile; - -import jakarta.validation.constraints.NotNull; - -@Schema(description = "管理后台 - 公众号素材上传图文内容中的图片 Request VO") -@Data -public class MpMaterialUploadNewsImageReqVO { - - @Schema(description = "公众号账号的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") - @NotNull(message = "公众号账号的编号不能为空") - private Long accountId; - - @Schema(description = "文件附件", requiredMode = Schema.RequiredMode.REQUIRED) - @NotNull(message = "文件不能为空") - @JsonIgnore // 避免被操作日志,进行序列化,导致报错 - private MultipartFile file; - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialUploadPermanentReqVO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialUploadPermanentReqVO.java deleted file mode 100644 index 8f4ff88..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialUploadPermanentReqVO.java +++ /dev/null @@ -1,53 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.material.vo; - -import cn.hutool.core.util.ObjectUtil; -import com.fasterxml.jackson.annotation.JsonIgnore; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import me.chanjar.weixin.common.api.WxConsts; -import org.springframework.web.multipart.MultipartFile; - -import jakarta.validation.constraints.AssertTrue; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; - -@Schema(description = "管理后台 - 公众号素材上传永久 Request VO") -@Data -public class MpMaterialUploadPermanentReqVO { - - @Schema(description = "公众号账号的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") - @NotNull(message = "公众号账号的编号不能为空") - private Long accountId; - - @Schema(description = "文件类型 参见 WxConsts.MediaFileType 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "image") - @NotEmpty(message = "文件类型不能为空") - private String type; - - @Schema(description = "文件附件", requiredMode = Schema.RequiredMode.REQUIRED) - @NotNull(message = "文件不能为空") - @JsonIgnore // 避免被操作日志,进行序列化,导致报错 - private MultipartFile file; - - @Schema(description = "名字 如果 name 为空,则使用 file 文件名", example = "wechat.mp") - private String name; - - @Schema(description = "视频素材的标题 文件类型为 video 时,必填", example = "视频素材的标题") - private String title; - @Schema(description = "视频素材的描述 文件类型为 video 时,必填", example = "视频素材的描述") - private String introduction; - - @AssertTrue(message = "标题不能为空") - public boolean isTitleValid() { - // 生成场景为管理后台时,必须设置上级菜单,不然生成的菜单 SQL 是无父级菜单的 - return ObjectUtil.notEqual(type, WxConsts.MediaFileType.VIDEO) - || title != null; - } - - @AssertTrue(message = "描述不能为空") - public boolean isIntroductionValid() { - // 生成场景为管理后台时,必须设置上级菜单,不然生成的菜单 SQL 是无父级菜单的 - return ObjectUtil.notEqual(type, WxConsts.MediaFileType.VIDEO) - || introduction != null; - } - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialUploadRespVO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialUploadRespVO.java deleted file mode 100644 index 0174cbc..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialUploadRespVO.java +++ /dev/null @@ -1,16 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.material.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -@Schema(description = "管理后台 - 公众号素材上传结果 Response VO") -@Data -public class MpMaterialUploadRespVO { - - @Schema(description = "素材的 media_id", requiredMode = Schema.RequiredMode.REQUIRED, example = "123") - private String mediaId; - - @Schema(description = "素材的 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.png") - private String url; - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialUploadTemporaryReqVO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialUploadTemporaryReqVO.java deleted file mode 100644 index 139828e..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialUploadTemporaryReqVO.java +++ /dev/null @@ -1,28 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.material.vo; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import org.springframework.web.multipart.MultipartFile; - -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; - -@Schema(description = "管理后台 - 公众号素材上传临时 Request VO") -@Data -public class MpMaterialUploadTemporaryReqVO { - - @Schema(description = "公众号账号的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") - @NotNull(message = "公众号账号的编号不能为空") - private Long accountId; - - @Schema(description = "文件类型 参见 WxConsts.MediaFileType 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "image") - @NotEmpty(message = "文件类型不能为空") - private String type; - - @Schema(description = "文件附件", requiredMode = Schema.RequiredMode.REQUIRED) - @NotNull(message = "文件不能为空") - @JsonIgnore // 避免被操作日志,进行序列化,导致报错 - private MultipartFile file; - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/MpMenuController.http b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/MpMenuController.http deleted file mode 100644 index defd7ec..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/MpMenuController.http +++ /dev/null @@ -1,50 +0,0 @@ -### 请求 /mp/menu/save 接口 => 成功 -POST {{baseUrl}}/mp/menu/save -Content-Type: application/json -Authorization: Bearer {{token}} -tenant-id: {{adminTenantId}} - -{ - "accountId": "1", - "menus": [ - { - "type":"click", - "name":"今日歌曲", - "menuKey":"V1001_TODAY_MUSIC" - }, - { - "name":"搜索", - "type":"view", - "url":"https://www.soso.com/" - }, - { - "name": "父按钮", - "children": [ - { - "type":"click", - "name":"归去来兮", - "menuKey":"MUSIC" - }, - { - "name":"不说", - "type":"view", - "url":"https://www.soso.com/" - }] - }] -} - -### 请求 /mp/menu/save 接口 => 成功(清空) -POST {{baseUrl}}/mp/menu/save -Content-Type: application/json -Authorization: Bearer {{token}} -tenant-id: {{adminTenantId}} - -{ - "accountId": "1", - "menus": [] -} - -### 请求 /mp/menu/list 接口 => 成功 -GET {{baseUrl}}/mp/menu/list?accountId=1 -Authorization: Bearer {{token}} -tenant-id: {{adminTenantId}} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/MpMenuController.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/MpMenuController.java deleted file mode 100644 index 28e321a..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/MpMenuController.java +++ /dev/null @@ -1,57 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.menu; - -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.module.mp.controller.admin.menu.vo.MpMenuRespVO; -import cn.iocoder.yudao.module.mp.controller.admin.menu.vo.MpMenuSaveReqVO; -import cn.iocoder.yudao.module.mp.convert.menu.MpMenuConvert; -import cn.iocoder.yudao.module.mp.dal.dataobject.menu.MpMenuDO; -import cn.iocoder.yudao.module.mp.service.menu.MpMenuService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.tags.Tag; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import jakarta.annotation.Resource; -import jakarta.validation.Valid; -import java.util.List; - -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; - -@Tag(name = "管理后台 - 公众号菜单") -@RestController -@RequestMapping("/mp/menu") -@Validated -public class MpMenuController { - - @Resource - private MpMenuService mpMenuService; - - @PostMapping("/save") - @Operation(summary = "保存公众号菜单") - @PreAuthorize("@ss.hasPermission('mp:menu:save')") - public CommonResult saveMenu(@Valid @RequestBody MpMenuSaveReqVO createReqVO) { - mpMenuService.saveMenu(createReqVO); - return success(true); - } - - @DeleteMapping("/delete") - @Operation(summary = "删除公众号菜单") - @Parameter(name = "accountId", description = "公众号账号的编号", required = true, example = "10") - @PreAuthorize("@ss.hasPermission('mp:menu:delete')") - public CommonResult deleteMenu(@RequestParam("accountId") Long accountId) { - mpMenuService.deleteMenuByAccountId(accountId); - return success(true); - } - - @GetMapping("/list") - @Operation(summary = "获得公众号菜单列表") - @Parameter(name = "accountId", description = "公众号账号的编号", required = true, example = "10") - @PreAuthorize("@ss.hasPermission('mp:menu:query')") - public CommonResult> getMenuList(@RequestParam("accountId") Long accountId) { - List list = mpMenuService.getMenuListByAccountId(accountId); - return success(MpMenuConvert.INSTANCE.convertList(list)); - } - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/vo/MpMenuBaseVO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/vo/MpMenuBaseVO.java deleted file mode 100644 index 8a6974c..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/vo/MpMenuBaseVO.java +++ /dev/null @@ -1,115 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.menu.vo; - -import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import me.chanjar.weixin.common.api.WxConsts; -import org.hibernate.validator.constraints.URL; - -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; -import java.util.List; - -import static cn.iocoder.yudao.module.mp.framework.mp.core.util.MpUtils.*; - -/** - * 公众号菜单 Base VO,提供给添加、修改、详细的子 VO 使用 - * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 - */ -@Data -public class MpMenuBaseVO { - - /** - * 菜单名称 - */ - private String name; - /** - * 菜单标识 - * - * 支持多 DB 类型时,无法直接使用 key + @TableField("menuKey") 来实现转换,原因是 "menuKey" AS key 而存在报错 - */ - private String menuKey; - /** - * 父菜单编号 - */ - private Long parentId; - - // ========== 按钮操作 ========== - - /** - * 按钮类型 - * - * 枚举 {@link WxConsts.MenuButtonType} - */ - private String type; - - @Schema(description = "网页链接", example = "https://www.iocoder.cn/") - @NotEmpty(message = "网页链接不能为空", groups = {ViewButtonGroup.class, MiniProgramButtonGroup.class}) - @URL(message = "网页链接必须是 URL 格式") - private String url; - - @Schema(description = "小程序的 appId", example = "wx1234567890") - @NotEmpty(message = "小程序的 appId 不能为空", groups = MiniProgramButtonGroup.class) - private String miniProgramAppId; - - @Schema(description = "小程序的页面路径", example = "pages/index/index") - @NotEmpty(message = "小程序的页面路径不能为空", groups = MiniProgramButtonGroup.class) - private String miniProgramPagePath; - - @Schema(description ="跳转图文的媒体编号", example = "jCQk93AIIgp8ixClWcW_NXXqBKInNWNmq2XnPeDZl7IMVqWiNeL4FfELtggRXd83") - @NotEmpty(message = "跳转图文的媒体编号不能为空", groups = ViewLimitedButtonGroup.class) - private String articleId; - - // ========== 消息内容 ========== - - @Schema(description = "回复的消息类型 枚举 TEXT、IMAGE、VOICE、VIDEO、NEWS、MUSIC", example = "text") - @NotEmpty(message = "回复的消息类型不能为空", groups = {ClickButtonGroup.class, ScanCodeWaitMsgButtonGroup.class}) - private String replyMessageType; - - @Schema(description = "回复的消息内容", example = "欢迎关注") - @NotEmpty(message = "回复的消息内容不能为空", groups = TextMessageGroup.class) - private String replyContent; - - @Schema(description = "回复的媒体 id", example = "123456") - @NotEmpty(message = "回复的消息 mediaId 不能为空", - groups = {ImageMessageGroup.class, VoiceMessageGroup.class, VideoMessageGroup.class}) - private String replyMediaId; - @Schema(description = "回复的媒体 URL", example = "https://www.iocoder.cn/xxx.jpg") - @NotEmpty(message = "回复的消息 mediaId 不能为空", - groups = {ImageMessageGroup.class, VoiceMessageGroup.class, VideoMessageGroup.class}) - private String replyMediaUrl; - - @Schema(description = "缩略图的媒体 id", example = "123456") - @NotEmpty(message = "回复的消息 thumbMediaId 不能为空", groups = {MusicMessageGroup.class}) - private String replyThumbMediaId; - @Schema(description = "缩略图的媒体 URL",example = "https://www.iocoder.cn/xxx.jpg") - @NotEmpty(message = "回复的消息 thumbMedia 地址不能为空", groups = {MusicMessageGroup.class}) - private String replyThumbMediaUrl; - - @Schema(description = "回复的标题", example = "视频标题") - @NotEmpty(message = "回复的消息标题不能为空", groups = VideoMessageGroup.class) - private String replyTitle; - @Schema(description = "回复的描述", example = "视频描述") - @NotEmpty(message = "消息描述不能为空", groups = VideoMessageGroup.class) - private String replyDescription; - - /** - * 回复的图文消息数组 - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS - */ - @NotNull(message = "回复的图文消息不能为空", groups = {NewsMessageGroup.class, ViewLimitedButtonGroup.class}) - @Valid - private List replyArticles; - - @Schema(description = "回复的音乐链接", example = "https://www.iocoder.cn/xxx.mp3") - @NotEmpty(message = "回复的音乐链接不能为空", groups = MusicMessageGroup.class) - @URL(message = "回复的高质量音乐链接格式不正确", groups = MusicMessageGroup.class) - private String replyMusicUrl; - @Schema(description = "高质量音乐链接", example = "https://www.iocoder.cn/xxx.mp3") - @NotEmpty(message = "回复的高质量音乐链接不能为空", groups = MusicMessageGroup.class) - @URL(message = "回复的高质量音乐链接格式不正确", groups = MusicMessageGroup.class) - private String replyHqMusicUrl; - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/vo/MpMenuRespVO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/vo/MpMenuRespVO.java deleted file mode 100644 index 5d54959..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/vo/MpMenuRespVO.java +++ /dev/null @@ -1,28 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.menu.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; - -import java.time.LocalDateTime; - -@Schema(description = "管理后台 - 公众号菜单 Response VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class MpMenuRespVO extends MpMenuBaseVO { - - @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - private Long id; - - @Schema(description = "公众号账号的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") - private Long accountId; - - @Schema(description = "公众号 appId", requiredMode = Schema.RequiredMode.REQUIRED, example = "wx1234567890ox") - private String appId; - - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) - private LocalDateTime createTime; - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/vo/MpMenuSaveReqVO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/vo/MpMenuSaveReqVO.java deleted file mode 100644 index 640c2c1..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/menu/vo/MpMenuSaveReqVO.java +++ /dev/null @@ -1,34 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.menu.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; -import java.util.List; - -@Schema(description = "管理后台 - 公众号菜单保存 Request VO") -@Data -public class MpMenuSaveReqVO { - - @Schema(description = "公众号账号的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") - @NotNull(message = "公众号账号的编号不能为空") - private Long accountId; - - @NotEmpty(message = "菜单不能为空") - @Valid - private List menus; - - @Schema(description = "管理后台 - 公众号菜单保存时的每个菜单") - @Data - public static class Menu extends MpMenuBaseVO { - - /** - * 子菜单数组 - */ - private List children; - - } - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpAutoReplyController.http b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpAutoReplyController.http deleted file mode 100644 index 8d17c6b..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpAutoReplyController.http +++ /dev/null @@ -1,5 +0,0 @@ -### 请求 /mp/message/page 接口 => 成功 -GET {{baseUrl}}/mp/auto-reply/page?accountId=1&pageNo=1&pageSize=10 -Content-Type: application/json -Authorization: Bearer {{token}} -tenant-id: {{adminTenantId}} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpAutoReplyController.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpAutoReplyController.java deleted file mode 100644 index 0bb7e17..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpAutoReplyController.java +++ /dev/null @@ -1,74 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.message; - -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.mp.controller.admin.message.vo.autoreply.MpAutoReplyCreateReqVO; -import cn.iocoder.yudao.module.mp.controller.admin.message.vo.autoreply.MpAutoReplyRespVO; -import cn.iocoder.yudao.module.mp.controller.admin.message.vo.autoreply.MpAutoReplyUpdateReqVO; -import cn.iocoder.yudao.module.mp.controller.admin.message.vo.message.MpMessagePageReqVO; -import cn.iocoder.yudao.module.mp.convert.message.MpAutoReplyConvert; -import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpAutoReplyDO; -import cn.iocoder.yudao.module.mp.service.message.MpAutoReplyService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.tags.Tag; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import jakarta.annotation.Resource; -import jakarta.validation.Valid; - -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; - -@Tag(name = "管理后台 - 公众号自动回复") -@RestController -@RequestMapping("/mp/auto-reply") -@Validated -public class MpAutoReplyController { - - @Resource - private MpAutoReplyService mpAutoReplyService; - - @GetMapping("/page") - @Operation(summary = "获得公众号自动回复分页") - @PreAuthorize("@ss.hasPermission('mp:auto-reply:query')") - public CommonResult> getAutoReplyPage(@Valid MpMessagePageReqVO pageVO) { - PageResult pageResult = mpAutoReplyService.getAutoReplyPage(pageVO); - return success(MpAutoReplyConvert.INSTANCE.convertPage(pageResult)); - } - - @GetMapping("/get") - @Operation(summary = "获得公众号自动回复") - @Parameter(name = "id", description = "编号", required = true, example = "1024") - @PreAuthorize("@ss.hasPermission('mp:auto-reply:query')") - public CommonResult getAutoReply(@RequestParam("id") Long id) { - MpAutoReplyDO autoReply = mpAutoReplyService.getAutoReply(id); - return success(MpAutoReplyConvert.INSTANCE.convert(autoReply)); - } - - @PostMapping("/create") - @Operation(summary = "创建公众号自动回复") - @PreAuthorize("@ss.hasPermission('mp:auto-reply:create')") - public CommonResult createAutoReply(@Valid @RequestBody MpAutoReplyCreateReqVO createReqVO) { - return success(mpAutoReplyService.createAutoReply(createReqVO)); - } - - @PutMapping("/update") - @Operation(summary = "更新公众号自动回复") - @PreAuthorize("@ss.hasPermission('mp:auto-reply:update')") - public CommonResult updateAutoReply(@Valid @RequestBody MpAutoReplyUpdateReqVO updateReqVO) { - mpAutoReplyService.updateAutoReply(updateReqVO); - return success(true); - } - - @DeleteMapping("/delete") - @Operation(summary = "删除公众号自动回复") - @Parameter(name = "id", description = "编号", required = true) - @PreAuthorize("@ss.hasPermission('mp:auto-reply:delete')") - public CommonResult deleteAutoReply(@RequestParam("id") Long id) { - mpAutoReplyService.deleteAutoReply(id); - return success(true); - } - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpMessageController.http b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpMessageController.http deleted file mode 100644 index 16677d4..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpMessageController.http +++ /dev/null @@ -1,33 +0,0 @@ -### 请求 /mp/message/page 接口 => 成功 -GET {{baseUrl}}/mp/message/page?accountId=1&pageNo=1&pageSize=10 -Content-Type: application/json -Authorization: Bearer {{token}} -tenant-id: {{adminTenantId}} - -### 请求 /mp/message/send 接口 => 成功(文本) -POST {{baseUrl}}/mp/message/send -Content-Type: application/json -Authorization: Bearer {{token}} -tenant-id: {{adminTenantId}} - -{ - "userId": 3, - "type": "text", - "content": "测试消息" -} - -### 请求 /mp/message/send 接口 => 成功(音乐) -POST {{baseUrl}}/mp/message/send -Content-Type: application/json -Authorization: Bearer {{token}} -tenant-id: {{adminTenantId}} - -{ - "userId": 3, - "type": "music", - "title": "测试音乐标题", - "description": "测试音乐内容", - "musicUrl": "https://www.iocoder.cn/xx.mp3", - "hqMusicUrl": "https://www.iocoder.cn/xx_high.mp3", - "thumbMediaId": "s98Iveeg9vDVFwa9q0u8-zSfdKe3xIzAm7wCrFE4WKGPIo4d9qAhtC-n6qvnyWyH" -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpMessageController.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpMessageController.java deleted file mode 100644 index 8c0bd32..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpMessageController.java +++ /dev/null @@ -1,47 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.message; - -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.mp.controller.admin.message.vo.message.MpMessagePageReqVO; -import cn.iocoder.yudao.module.mp.controller.admin.message.vo.message.MpMessageRespVO; -import cn.iocoder.yudao.module.mp.controller.admin.message.vo.message.MpMessageSendReqVO; -import cn.iocoder.yudao.module.mp.convert.message.MpMessageConvert; -import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO; -import cn.iocoder.yudao.module.mp.service.message.MpMessageService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import jakarta.annotation.Resource; -import jakarta.validation.Valid; - -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; - -@Tag(name = "管理后台 - 公众号消息") -@RestController -@RequestMapping("/mp/message") -@Validated -public class MpMessageController { - - @Resource - private MpMessageService mpMessageService; - - @GetMapping("/page") - @Operation(summary = "获得公众号消息分页") - @PreAuthorize("@ss.hasPermission('mp:message:query')") - public CommonResult> getMessagePage(@Valid MpMessagePageReqVO pageVO) { - PageResult pageResult = mpMessageService.getMessagePage(pageVO); - return success(MpMessageConvert.INSTANCE.convertPage(pageResult)); - } - - @PostMapping("/send") - @Operation(summary = "给粉丝发送消息") - @PreAuthorize("@ss.hasPermission('mp:message:send')") - public CommonResult sendMessage(@Valid @RequestBody MpMessageSendReqVO reqVO) { - MpMessageDO message = mpMessageService.sendKefuMessage(reqVO); - return success(MpMessageConvert.INSTANCE.convert(message)); - } - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyBaseVO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyBaseVO.java deleted file mode 100644 index fceb5f7..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyBaseVO.java +++ /dev/null @@ -1,109 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.message.vo.autoreply; - -import cn.hutool.core.util.ObjectUtil; -import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO; -import cn.iocoder.yudao.module.mp.enums.message.MpAutoReplyTypeEnum; -import cn.iocoder.yudao.module.mp.framework.mp.core.util.MpUtils.*; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import me.chanjar.weixin.common.api.WxConsts; -import org.hibernate.validator.constraints.URL; - -import jakarta.validation.Valid; -import jakarta.validation.constraints.AssertTrue; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; -import java.util.List; - -/** - * 公众号自动回复 Base VO,提供给添加、修改、详细的子 VO 使用 - * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 - */ -@Data -public class MpAutoReplyBaseVO { - - @Schema(description = "回复类型 参见 MpAutoReplyTypeEnum 枚举", example = "1") - @NotNull(message = "回复类型不能为空") - private Integer type; - - // ==================== 请求消息 ==================== - - @Schema(description = "请求的关键字 当 type 为 MpAutoReplyTypeEnum#KEYWORD 时,必填", example = "关键字") - private String requestKeyword; - @Schema(description = "请求的匹配方式 当 type 为 MpAutoReplyTypeEnum#KEYWORD 时,必填", example = "1") - private Integer requestMatch; - - @Schema(description = "请求的消息类型 当 type 为 MpAutoReplyTypeEnum#MESSAGE 时,必填", example = "text") - private String requestMessageType; - - // ==================== 响应消息 ==================== - - @Schema(description = "回复的消息类型 枚举 TEXT、IMAGE、VOICE、VIDEO、NEWS、MUSIC", example = "text") - @NotEmpty(message = "回复的消息类型不能为空") - private String responseMessageType; - - @Schema(description = "回复的消息内容", example = "欢迎关注") - @NotEmpty(message = "回复的消息内容不能为空", groups = TextMessageGroup.class) - private String responseContent; - - @Schema(description = "回复的媒体 id", example = "123456") - @NotEmpty(message = "回复的消息 mediaId 不能为空", - groups = {ImageMessageGroup.class, VoiceMessageGroup.class, VideoMessageGroup.class}) - private String responseMediaId; - @Schema(description = "回复的媒体 URL", example = "https://www.iocoder.cn/xxx.jpg") - @NotEmpty(message = "回复的消息 mediaId 不能为空", - groups = {ImageMessageGroup.class, VoiceMessageGroup.class, VideoMessageGroup.class}) - private String responseMediaUrl; - - @Schema(description = "缩略图的媒体 id", example = "123456") - @NotEmpty(message = "回复的消息 thumbMediaId 不能为空", groups = {MusicMessageGroup.class}) - private String responseThumbMediaId; - @Schema(description = "缩略图的媒体 URL",example = "https://www.iocoder.cn/xxx.jpg") - @NotEmpty(message = "回复的消息 thumbMedia 地址不能为空", groups = {MusicMessageGroup.class}) - private String responseThumbMediaUrl; - - @Schema(description = "回复的标题", example = "视频标题") - @NotEmpty(message = "回复的消息标题不能为空", groups = VideoMessageGroup.class) - private String responseTitle; - @Schema(description = "回复的描述", example = "视频描述") - @NotEmpty(message = "消息描述不能为空", groups = VideoMessageGroup.class) - private String responseDescription; - - /** - * 回复的图文消息 - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS - */ - @NotNull(message = "回复的图文消息不能为空", groups = {NewsMessageGroup.class, ViewLimitedButtonGroup.class}) - @Valid - private List responseArticles; - - @Schema(description = "回复的音乐链接", example = "https://www.iocoder.cn/xxx.mp3") - @NotEmpty(message = "回复的音乐链接不能为空", groups = MusicMessageGroup.class) - @URL(message = "回复的高质量音乐链接格式不正确", groups = MusicMessageGroup.class) - private String responseMusicUrl; - @Schema(description = "高质量音乐链接", example = "https://www.iocoder.cn/xxx.mp3") - @NotEmpty(message = "回复的高质量音乐链接不能为空", groups = MusicMessageGroup.class) - @URL(message = "回复的高质量音乐链接格式不正确", groups = MusicMessageGroup.class) - private String responseHqMusicUrl; - - @AssertTrue(message = "请求的关键字不能为空") - public boolean isRequestKeywordValid() { - return ObjectUtil.notEqual(type, MpAutoReplyTypeEnum.KEYWORD) - || requestKeyword != null; - } - - @AssertTrue(message = "请求的关键字的匹配不能为空") - public boolean isRequestMatchValid() { - return ObjectUtil.notEqual(type, MpAutoReplyTypeEnum.KEYWORD) - || requestMatch != null; - } - - @AssertTrue(message = "请求的消息类型不能为空") - public boolean isRequestMessageTypeValid() { - return ObjectUtil.notEqual(type, MpAutoReplyTypeEnum.MESSAGE) - || requestMessageType != null; - } - - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyCreateReqVO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyCreateReqVO.java deleted file mode 100644 index 502c17d..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyCreateReqVO.java +++ /dev/null @@ -1,20 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.message.vo.autoreply; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; - -import jakarta.validation.constraints.NotNull; - -@Schema(description = "管理后台 - 公众号自动回复的创建 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class MpAutoReplyCreateReqVO extends MpAutoReplyBaseVO { - - @Schema(description = "公众号账号的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - @NotNull(message = "公众号账号的编号不能为空") - private Long accountId; - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyPageReqVO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyPageReqVO.java deleted file mode 100644 index 70169f4..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyPageReqVO.java +++ /dev/null @@ -1,21 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.message.vo.autoreply; - -import cn.iocoder.yudao.framework.common.pojo.PageParam; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; - -import jakarta.validation.constraints.NotNull; - -@Schema(description = "管理后台 - 公众号自动回复的分页 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class MpAutoReplyPageReqVO extends PageParam { - - @Schema(description = "公众号账号的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - @NotNull(message = "公众号账号的编号不能为空") - private Long accountId; - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyRespVO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyRespVO.java deleted file mode 100644 index d1f72f3..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyRespVO.java +++ /dev/null @@ -1,27 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.message.vo.autoreply; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; - -import java.time.LocalDateTime; - -@Schema(description = "管理后台 - 公众号自动回复 Response VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class MpAutoReplyRespVO extends MpAutoReplyBaseVO { - - @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - private Long id; - - @Schema(description = "公众号账号的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - private Long accountId; - @Schema(description = "公众号 appId", requiredMode = Schema.RequiredMode.REQUIRED, example = "wx1234567890") - private String appId; - - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) - private LocalDateTime createTime; - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyUpdateReqVO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyUpdateReqVO.java deleted file mode 100644 index 776400c..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyUpdateReqVO.java +++ /dev/null @@ -1,20 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.message.vo.autoreply; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; - -import jakarta.validation.constraints.NotNull; - -@Schema(description = "管理后台 - 公众号自动回复的更新 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class MpAutoReplyUpdateReqVO extends MpAutoReplyBaseVO { - - @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - @NotNull(message = "主键不能为空") - private Long id; - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/message/MpMessagePageReqVO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/message/MpMessagePageReqVO.java deleted file mode 100644 index d9f7cc8..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/message/MpMessagePageReqVO.java +++ /dev/null @@ -1,35 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.message.vo.message; - -import cn.iocoder.yudao.framework.common.pojo.PageParam; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; -import org.springframework.format.annotation.DateTimeFormat; - -import jakarta.validation.constraints.NotNull; -import java.time.LocalDateTime; - -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; - -@Schema(description = "管理后台 - 公众号消息分页 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class MpMessagePageReqVO extends PageParam { - - @Schema(description = "公众号账号的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - @NotNull(message = "公众号账号的编号不能为空") - private Long accountId; - - @Schema(description = "消息类型 参见 WxConsts.XmlMsgType 枚举", example = "text") - private String type; - - @Schema(description = "公众号粉丝标识", example = "o6_bmjrPTlm6_2sgVt7hMZOPfL2M") - private String openid; - - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) - @Schema(description = "创建时间") - private LocalDateTime[] createTime; - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/message/MpMessageRespVO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/message/MpMessageRespVO.java deleted file mode 100644 index fda5d26..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/message/MpMessageRespVO.java +++ /dev/null @@ -1,101 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.message.vo.message; - -import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO; -import com.baomidou.mybatisplus.annotation.TableField; -import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import me.chanjar.weixin.common.api.WxConsts; - -import java.time.LocalDateTime; -import java.util.List; - -@Schema(description = "管理后台 - 公众号消息 Response VO") -@Data -public class MpMessageRespVO { - - @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - private Integer id; - - @Schema(description = "微信公众号消息 id", requiredMode = Schema.RequiredMode.REQUIRED, example = "23953173569869169") - private Long msgId; - - @Schema(description = "公众号账号的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - private Long accountId; - @Schema(description = "公众号账号的 appid", requiredMode = Schema.RequiredMode.REQUIRED, example = "wx1234567890") - private String appId; - - @Schema(description = "公众号粉丝编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") - private Long userId; - @Schema(description = "公众号粉丝标志", requiredMode = Schema.RequiredMode.REQUIRED, example = "o6_bmjrPTlm6_2sgVt7hMZOPfL2M") - private String openid; - - @Schema(description = "消息类型 参见 WxConsts.XmlMsgType 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "text") - private String type; - @Schema(description = "消息来源 参见 MpMessageSendFromEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - private Integer sendFrom; - - // ========= 普通消息内容 https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_standard_messages.html - - @Schema(description = "消息内容 消息类型为 text 时,才有值", example = "你好呀") - private String content; - - @Schema(description = "媒体素材的编号 消息类型为 image、voice、video 时,才有值", example = "1234567890") - private String mediaId; - @Schema(description = "媒体文件的 URL 消息类型为 image、voice、video 时,才有值", example = "https://www.iocoder.cn/xxx.png") - private String mediaUrl; - - @Schema(description = "语音识别后文本 消息类型为 voice 时,才有值", example = "语音识别后文本") - private String recognition; - @Schema(description = "语音格式 消息类型为 voice 时,才有值", example = "amr") - private String format; - - @Schema(description = "标题 消息类型为 video、music、link 时,才有值", example = "我是标题") - private String title; - - @Schema(description = "描述 消息类型为 video、music 时,才有值", example = "我是描述") - private String description; - - @Schema(description = "缩略图的媒体 id 消息类型为 video、music 时,才有值", example = "1234567890") - private String thumbMediaId; - @Schema(description = "缩略图的媒体 URL 消息类型为 video、music 时,才有值", example = "https://www.iocoder.cn/xxx.png") - private String thumbMediaUrl; - - @Schema(description = "点击图文消息跳转链接 消息类型为 link 时,才有值", example = "https://www.iocoder.cn") - private String url; - - @Schema(description = "地理位置维度 消息类型为 location 时,才有值", example = "23.137466") - private Double locationX; - - @Schema(description = "地理位置经度 消息类型为 location 时,才有值", example = "113.352425") - private Double locationY; - - @Schema(description = "地图缩放大小 消息类型为 location 时,才有值", example = "13") - private Double scale; - - @Schema(description = "详细地址 消息类型为 location 时,才有值", example = "杨浦区黄兴路 221-4 号临") - private String label; - - /** - * 图文消息数组 - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS - */ - private List articles; - - @Schema(description = "音乐链接 消息类型为 music 时,才有值", example = "https://www.iocoder.cn/xxx.mp3") - private String musicUrl; - @Schema(description = "高质量音乐链接 消息类型为 music 时,才有值", example = "https://www.iocoder.cn/xxx.mp3") - private String hqMusicUrl; - - // ========= 事件推送 https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_event_pushes.html - - @Schema(description = "事件类型 参见 WxConsts.EventType 枚举", example = "subscribe") - private String event; - @Schema(description = "事件 Key 参见 WxConsts.EventType 枚举", example = "qrscene_123456") - private String eventKey; - - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) - private LocalDateTime createTime; - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/message/MpMessageSendReqVO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/message/MpMessageSendReqVO.java deleted file mode 100644 index 0c96392..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/message/MpMessageSendReqVO.java +++ /dev/null @@ -1,58 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.message.vo.message; - -import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO; -import cn.iocoder.yudao.module.mp.framework.mp.core.util.MpUtils.*; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; -import java.util.List; - -@Schema(description = "管理后台 - 公众号消息发送 Request VO") -@Data -public class MpMessageSendReqVO { - - @Schema(description = "公众号粉丝的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - @NotNull(message = "公众号粉丝的编号不能为空") - private Long userId; - - // ========== 消息内容 ========== - - @Schema(description = "消息类型 TEXT/IMAGE/VOICE/VIDEO/NEWS", requiredMode = Schema.RequiredMode.REQUIRED, example = "text") - @NotEmpty(message = "消息类型不能为空") - public String type; - - @Schema(description = "消息内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "你好呀") - @NotEmpty(message = "消息内容不能为空", groups = TextMessageGroup.class) - private String content; - - @Schema(description = "媒体 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "qqc_2Fot30Jse-HDoZmo5RrUDijz2nGUkP") - @NotEmpty(message = "消息内容不能为空", groups = {ImageMessageGroup.class, VoiceMessageGroup.class, VideoMessageGroup.class}) - private String mediaId; - - @Schema(description = "标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "没有标题") - @NotEmpty(message = "消息内容不能为空", groups = VideoMessageGroup.class) - private String title; - - @Schema(description = "描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "你猜") - @NotEmpty(message = "消息描述不能为空", groups = VideoMessageGroup.class) - private String description; - - @Schema(description = "缩略图的媒体 id", requiredMode = Schema.RequiredMode.REQUIRED, example = "qqc_2Fot30Jse-HDoZmo5RrUDijz2nGUkP") - @NotEmpty(message = "缩略图的媒体 id 不能为空", groups = MusicMessageGroup.class) - private String thumbMediaId; - - @Schema(description = "图文消息", requiredMode = Schema.RequiredMode.REQUIRED) - @Valid - @NotNull(message = "图文消息不能为空", groups = NewsMessageGroup.class) - private List articles; - - @Schema(description = "音乐链接 消息类型为 MUSIC 时", example = "https://www.iocoder.cn/music.mp3") - private String musicUrl; - - @Schema(description = "高质量音乐链接 消息类型为 MUSIC 时", example = "https://www.iocoder.cn/music.mp3") - private String hqMusicUrl; - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/MpDraftController.http b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/MpDraftController.http deleted file mode 100644 index ea75429..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/MpDraftController.http +++ /dev/null @@ -1,54 +0,0 @@ -### 请求 /mp/draft/page 接口 => 成功 -GET {{baseUrl}}/mp/draft/page?accountId=1&pageNo=1&pageSize=10 -Content-Type: application/json -Authorization: Bearer {{token}} -tenant-id: {{adminTenantId}} - -### 请求 /mp/draft/create 接口 => 成功 -POST {{baseUrl}}/mp/draft/create?accountId=1 -Content-Type: application/json -Authorization: Bearer {{token}} -tenant-id: {{adminTenantId}} - -{ - "articles": [ - { - "title": "我是标题", - "author": "我是作者", - "digest": "我是摘要", - "content": "我是内容", - "contentSourceUrl": "https://www.iocoder.cn", - "thumbMediaId": "r6ryvl6LrxBU0miaST4Y-pIcmK-zAAId-9TGgy-DrSLhjVuWbuT3ZBjk9K1yQ0Dn" - }, - { - "title": "我是标题 2", - "author": "我是作者 2", - "digest": "我是摘要 2", - "content": "我是内容 2", - "contentSourceUrl": "https://www.iocoder.cn", - "thumbMediaId": "r6ryvl6LrxBU0miaST4Y-pIcmK-zAAId-9TGgy-DrSLhjVuWbuT3ZBjk9K1yQ0Dn" - } - ] -} - -### 请求 /mp/draft/create 接口 => 成功 -PUT {{baseUrl}}/mp/draft/update?accountId=1&mediaId=r6ryvl6LrxBU0miaST4Y-q-G9pdsmZw0OYG4FzHQkKfpLfEwIH51wy2bxisx8PvW -Content-Type: application/json -Authorization: Bearer {{token}} -tenant-id: {{adminTenantId}} - -[{ - "title": "我是标题(OOO)", - "author": "我是作者", - "digest": "我是摘要", - "content": "我是内容", - "contentSourceUrl": "https://www.iocoder.cn", - "thumbMediaId": "r6ryvl6LrxBU0miaST4Y-pIcmK-zAAId-9TGgy-DrSLhjVuWbuT3ZBjk9K1yQ0Dn" -}, { - "title": "我是标题(XXX)", - "author": "我是作者", - "digest": "我是摘要", - "content": "我是内容", - "contentSourceUrl": "https://www.iocoder.cn", - "thumbMediaId": "r6ryvl6LrxBU0miaST4Y-pIcmK-zAAId-9TGgy-DrSLhjVuWbuT3ZBjk9K1yQ0Dn" -}] diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/MpDraftController.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/MpDraftController.java deleted file mode 100644 index d113cef..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/MpDraftController.java +++ /dev/null @@ -1,136 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.news; - -import cn.hutool.core.collection.CollUtil; -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; -import cn.iocoder.yudao.framework.common.util.object.PageUtils; -import cn.iocoder.yudao.module.mp.controller.admin.news.vo.MpDraftPageReqVO; -import cn.iocoder.yudao.module.mp.dal.dataobject.material.MpMaterialDO; -import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory; -import cn.iocoder.yudao.module.mp.service.material.MpMaterialService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.Parameters; -import io.swagger.v3.oas.annotations.tags.Tag; -import me.chanjar.weixin.common.error.WxErrorException; -import me.chanjar.weixin.mp.api.WxMpService; -import me.chanjar.weixin.mp.bean.draft.*; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import jakarta.annotation.Resource; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; -import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen; -import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.*; - -@Tag(name = "管理后台 - 公众号草稿") -@RestController -@RequestMapping("/mp/draft") -@Validated -public class MpDraftController { - - @Resource - private MpServiceFactory mpServiceFactory; - - @Resource - private MpMaterialService mpMaterialService; - - @GetMapping("/page") - @Operation(summary = "获得草稿分页") - @PreAuthorize("@ss.hasPermission('mp:draft:query')") - public CommonResult> getDraftPage(MpDraftPageReqVO reqVO) { - // 从公众号查询草稿箱 - WxMpService mpService = mpServiceFactory.getRequiredMpService(reqVO.getAccountId()); - WxMpDraftList draftList; - try { - draftList = mpService.getDraftService().listDraft(PageUtils.getStart(reqVO), reqVO.getPageSize()); - } catch (WxErrorException e) { - throw exception(DRAFT_LIST_FAIL, e.getError().getErrorMsg()); - } - // 查询对应的图片地址。目的:解决公众号的图片链接无法在我们后台展示 - setDraftThumbUrl(draftList.getItems()); - - // 返回分页 - return success(new PageResult<>(draftList.getItems(), draftList.getTotalCount().longValue())); - } - - private void setDraftThumbUrl(List items) { - // 1.1 获得 mediaId 数组 - Set mediaIds = new HashSet<>(); - items.forEach(item -> item.getContent().getNewsItem().forEach(newsItem -> mediaIds.add(newsItem.getThumbMediaId()))); - if (CollUtil.isEmpty(mediaIds)) { - return; - } - // 1.2 批量查询对应的 Media 素材 - Map materials = CollectionUtils.convertMap(mpMaterialService.getMaterialListByMediaId(mediaIds), - MpMaterialDO::getMediaId); - - // 2. 设置回 WxMpDraftItem 记录 - items.forEach(item -> item.getContent().getNewsItem().forEach(newsItem -> - findAndThen(materials, newsItem.getThumbMediaId(), material -> newsItem.setThumbUrl(material.getUrl())))); - } - - @PostMapping("/create") - @Operation(summary = "创建草稿") - @Parameter(name = "accountId", description = "公众号账号的编号", required = true, example = "1024") - @PreAuthorize("@ss.hasPermission('mp:draft:create')") - public CommonResult deleteDraft(@RequestParam("accountId") Long accountId, - @RequestBody WxMpAddDraft draft) { - WxMpService mpService = mpServiceFactory.getRequiredMpService(accountId); - try { - String mediaId = mpService.getDraftService().addDraft(draft); - return success(mediaId); - } catch (WxErrorException e) { - throw exception(DRAFT_CREATE_FAIL, e.getError().getErrorMsg()); - } - } - - @PutMapping("/update") - @Operation(summary = "更新草稿") - @Parameters({ - @Parameter(name = "accountId", description = "公众号账号的编号", required = true, example = "1024"), - @Parameter(name = "mediaId", description = "草稿素材的编号", required = true, example = "xxx") - }) - @PreAuthorize("@ss.hasPermission('mp:draft:update')") - public CommonResult deleteDraft(@RequestParam("accountId") Long accountId, - @RequestParam("mediaId") String mediaId, - @RequestBody List articles) { - WxMpService mpService = mpServiceFactory.getRequiredMpService(accountId); - try { - for (int i = 0; i < articles.size(); i++) { - WxMpDraftArticles article = articles.get(i); - mpService.getDraftService().updateDraft(new WxMpUpdateDraft(mediaId, i, article)); - } - return success(true); - } catch (WxErrorException e) { - throw exception(DRAFT_UPDATE_FAIL, e.getError().getErrorMsg()); - } - } - - @DeleteMapping("/delete") - @Operation(summary = "删除草稿") - @Parameters({ - @Parameter(name = "accountId", description = "公众号账号的编号", required = true, example = "1024"), - @Parameter(name = "mediaId", description = "草稿素材的编号", required = true, example = "xxx") - }) - @PreAuthorize("@ss.hasPermission('mp:draft:delete')") - public CommonResult deleteDraft(@RequestParam("accountId") Long accountId, - @RequestParam("mediaId") String mediaId) { - WxMpService mpService = mpServiceFactory.getRequiredMpService(accountId); - try { - mpService.getDraftService().delDraft(mediaId); - return success(true); - } catch (WxErrorException e) { - throw exception(DRAFT_DELETE_FAIL, e.getError().getErrorMsg()); - } - } - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/MpFreePublishController.http b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/MpFreePublishController.http deleted file mode 100644 index 246a95c..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/MpFreePublishController.http +++ /dev/null @@ -1,13 +0,0 @@ -### 请求 /mp/free-publish/page 接口 => 成功 -GET {{baseUrl}}/mp/free-publish/page?accountId=1&pageNo=1&pageSize=10 -Content-Type: application/json -Authorization: Bearer {{token}} -tenant-id: {{adminTenantId}} - -### 请求 /mp/free-publish/submit 接口 => 成功 -POST {{baseUrl}}/mp/free-publish/submit?accountId=1&mediaId=r6ryvl6LrxBU0miaST4Y-vilmd7iS51D8IPddxflWrau0hIQ2ovY8YanO5jlgUcM -Content-Type: application/json -Authorization: Bearer {{token}} -tenant-id: {{adminTenantId}} - -{} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/MpFreePublishController.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/MpFreePublishController.java deleted file mode 100644 index 099677f..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/MpFreePublishController.java +++ /dev/null @@ -1,119 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.news; - -import cn.hutool.core.collection.CollUtil; -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; -import cn.iocoder.yudao.framework.common.util.object.PageUtils; -import cn.iocoder.yudao.module.mp.controller.admin.news.vo.MpFreePublishPageReqVO; -import cn.iocoder.yudao.module.mp.dal.dataobject.material.MpMaterialDO; -import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory; -import cn.iocoder.yudao.module.mp.service.material.MpMaterialService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.Parameters; -import io.swagger.v3.oas.annotations.tags.Tag; -import me.chanjar.weixin.common.error.WxErrorException; -import me.chanjar.weixin.mp.api.WxMpService; -import me.chanjar.weixin.mp.bean.freepublish.WxMpFreePublishItem; -import me.chanjar.weixin.mp.bean.freepublish.WxMpFreePublishList; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import jakarta.annotation.Resource; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; -import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen; -import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.*; - -@Tag(name = "管理后台 - 公众号发布能力") -@RestController -@RequestMapping("/mp/free-publish") -@Validated -public class MpFreePublishController { - - @Resource - private MpServiceFactory mpServiceFactory; - - @Resource - private MpMaterialService mpMaterialService; - - @GetMapping("/page") - @Operation(summary = "获得已发布的图文分页") - @PreAuthorize("@ss.hasPermission('mp:free-publish:query')") - public CommonResult> getFreePublishPage(MpFreePublishPageReqVO reqVO) { - // 从公众号查询已发布的图文列表 - WxMpService mpService = mpServiceFactory.getRequiredMpService(reqVO.getAccountId()); - WxMpFreePublishList publicationRecords; - try { - publicationRecords = mpService.getFreePublishService().getPublicationRecords( - PageUtils.getStart(reqVO), reqVO.getPageSize()); - } catch (WxErrorException e) { - throw exception(FREE_PUBLISH_LIST_FAIL, e.getError().getErrorMsg()); - } - // 查询对应的图片地址。目的:解决公众号的图片链接无法在我们后台展示 - setFreePublishThumbUrl(publicationRecords.getItems()); - - // 返回分页 - return success(new PageResult<>(publicationRecords.getItems(), publicationRecords.getTotalCount().longValue())); - } - - private void setFreePublishThumbUrl(List items) { - // 1.1 获得 mediaId 数组 - Set mediaIds = new HashSet<>(); - items.forEach(item -> item.getContent().getNewsItem().forEach(newsItem -> mediaIds.add(newsItem.getThumbMediaId()))); - if (CollUtil.isEmpty(mediaIds)) { - return; - } - // 1.2 批量查询对应的 Media 素材 - Map materials = CollectionUtils.convertMap(mpMaterialService.getMaterialListByMediaId(mediaIds), - MpMaterialDO::getMediaId); - - // 2. 设置回 WxMpFreePublishItem 记录 - items.forEach(item -> item.getContent().getNewsItem().forEach(newsItem -> - findAndThen(materials, newsItem.getThumbMediaId(), material -> newsItem.setThumbUrl(material.getUrl())))); - } - - @PostMapping("/submit") - @Operation(summary = "发布草稿") - @Parameters({ - @Parameter(name = "accountId", description = "公众号账号的编号", required = true, example = "1024"), - @Parameter(name = "mediaId", description = "要发布的草稿的 media_id", required = true, example = "2048") - }) - @PreAuthorize("@ss.hasPermission('mp:free-publish:submit')") - public CommonResult submitFreePublish(@RequestParam("accountId") Long accountId, - @RequestParam("mediaId") String mediaId) { - WxMpService mpService = mpServiceFactory.getRequiredMpService(accountId); - try { - String publishId = mpService.getFreePublishService().submit(mediaId); - return success(publishId); - } catch (WxErrorException e) { - throw exception(FREE_PUBLISH_SUBMIT_FAIL, e.getError().getErrorMsg()); - } - } - - @DeleteMapping("/delete") - @Operation(summary = "删除草稿") - @Parameters({ - @Parameter(name = "accountId", description = "公众号账号的编号", required = true, example = "1024"), - @Parameter(name = "articleId", description = "发布记录的编号", required = true, example = "2048") - }) - @PreAuthorize("@ss.hasPermission('mp:free-publish:delete')") - public CommonResult deleteFreePublish(@RequestParam("accountId") Long accountId, - @RequestParam("articleId") String articleId) { - WxMpService mpService = mpServiceFactory.getRequiredMpService(accountId); - try { - mpService.getFreePublishService().deletePushAllArticle(articleId); - return success(true); - } catch (WxErrorException e) { - throw exception(FREE_PUBLISH_DELETE_FAIL, e.getError().getErrorMsg()); - } - } - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/vo/MpDraftPageReqVO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/vo/MpDraftPageReqVO.java deleted file mode 100644 index 3180257..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/vo/MpDraftPageReqVO.java +++ /dev/null @@ -1,21 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.news.vo; - -import cn.iocoder.yudao.framework.common.pojo.PageParam; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; - -import jakarta.validation.constraints.NotNull; - -@Schema(description = "管理后台 - 公众号草稿的分页 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class MpDraftPageReqVO extends PageParam { - - @Schema(description = "公众号账号的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - @NotNull(message = "公众号账号的编号不能为空") - private Long accountId; - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/vo/MpFreePublishPageReqVO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/vo/MpFreePublishPageReqVO.java deleted file mode 100644 index 9f35edb..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/news/vo/MpFreePublishPageReqVO.java +++ /dev/null @@ -1,21 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.news.vo; - -import cn.iocoder.yudao.framework.common.pojo.PageParam; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; - -import jakarta.validation.constraints.NotNull; - -@Schema(description = "管理后台 - 公众号已发布列表的分页 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class MpFreePublishPageReqVO extends PageParam { - - @Schema(description = "公众号账号的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - @NotNull(message = "公众号账号的编号不能为空") - private Long accountId; - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/open/MpOpenController.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/open/MpOpenController.java deleted file mode 100644 index d1a1e3f..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/open/MpOpenController.java +++ /dev/null @@ -1,117 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.open; - -import cn.hutool.core.lang.Assert; -import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; -import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; -import cn.iocoder.yudao.module.mp.controller.admin.open.vo.MpOpenCheckSignatureReqVO; -import cn.iocoder.yudao.module.mp.controller.admin.open.vo.MpOpenHandleMessageReqVO; -import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; -import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory; -import cn.iocoder.yudao.module.mp.framework.mp.core.context.MpContextHolder; -import cn.iocoder.yudao.module.mp.service.account.MpAccountService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.extern.slf4j.Slf4j; -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.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import jakarta.annotation.Resource; -import java.util.Objects; - -@Tag(name = "管理后台 - 公众号回调") -@RestController -@RequestMapping("/mp/open") -@Validated -@Slf4j -public class MpOpenController { - - @Resource - private MpServiceFactory mpServiceFactory; - - @Resource - private MpAccountService mpAccountService; - - /** - * 接收微信公众号的消息推送 - * - * 文档 - */ - @Operation(summary = "处理消息") - @PostMapping(value = "/{appId}", produces = "application/xml; charset=UTF-8") - @TenantIgnore - public String handleMessage(@PathVariable("appId") String appId, - @RequestBody String content, - MpOpenHandleMessageReqVO reqVO) { - log.info("[handleMessage][appId({}) 推送消息,参数({}) 内容({})]", appId, reqVO, content); - - // 处理 appId + 多租户的上下文 - MpAccountDO account = mpAccountService.getAccountFromCache(appId); - Assert.notNull(account, "公众号 appId({}) 不存在", appId); - try { - MpContextHolder.setAppId(appId); - return TenantUtils.execute(account.getTenantId(), - () -> handleMessage0(appId, content, reqVO)); - } finally { - MpContextHolder.clear(); - } - } - - /** - * 接收微信公众号的校验签名 - * - * 对应 文档 - */ - @Operation(summary = "校验签名") // 参见 - @GetMapping(value = "/{appId}", produces = "text/plain;charset=utf-8") - @TenantIgnore - public String checkSignature(@PathVariable("appId") String appId, - MpOpenCheckSignatureReqVO reqVO) { - log.info("[checkSignature][appId({}) 接收到来自微信服务器的认证消息({})]", appId, reqVO); - // 校验请求签名 - WxMpService wxMpService = mpServiceFactory.getRequiredMpService(appId); - // 校验通过 - if (wxMpService.checkSignature(reqVO.getTimestamp(), reqVO.getNonce(), reqVO.getSignature())) { - return reqVO.getEchostr(); - } - // 校验不通过 - return "非法请求"; - } - - private String handleMessage0(String appId, String content, MpOpenHandleMessageReqVO reqVO) { - // 校验请求签名 - WxMpService mppService = mpServiceFactory.getRequiredMpService(appId); - Assert.isTrue(mppService.checkSignature(reqVO.getTimestamp(), reqVO.getNonce(), reqVO.getSignature()), - "非法请求"); - - // 第一步,解析消息 - WxMpXmlMessage inMessage = null; - if (StrUtil.isBlank(reqVO.getEncrypt_type())) { // 明文模式 - inMessage = WxMpXmlMessage.fromXml(content); - } else if (Objects.equals(reqVO.getEncrypt_type(), MpOpenHandleMessageReqVO.ENCRYPT_TYPE_AES)) { // AES 加密模式 - inMessage = WxMpXmlMessage.fromEncryptedXml(content, mppService.getWxMpConfigStorage(), - reqVO.getTimestamp(), reqVO.getNonce(), reqVO.getMsg_signature()); - } - Assert.notNull(inMessage, "消息解析失败,原因:消息为空"); - - // 第二步,处理消息 - WxMpMessageRouter mpMessageRouter = mpServiceFactory.getRequiredMpMessageRouter(appId); - WxMpXmlOutMessage outMessage = mpMessageRouter.route(inMessage); - if (outMessage == null) { - return ""; - } - - // 第三步,返回消息 - if (StrUtil.isBlank(reqVO.getEncrypt_type())) { // 明文模式 - return outMessage.toXml(); - } else if (Objects.equals(reqVO.getEncrypt_type(), MpOpenHandleMessageReqVO.ENCRYPT_TYPE_AES)) { // AES 加密模式 - return outMessage.toEncryptedXml(mppService.getWxMpConfigStorage()); - } - return ""; - } - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/open/vo/MpOpenCheckSignatureReqVO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/open/vo/MpOpenCheckSignatureReqVO.java deleted file mode 100644 index 0dd9953..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/open/vo/MpOpenCheckSignatureReqVO.java +++ /dev/null @@ -1,29 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.open.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import jakarta.validation.constraints.NotEmpty; - -@Schema(description = "管理后台 - 公众号校验签名 Request VO") -@Data -public class MpOpenCheckSignatureReqVO { - - @Schema(description = "微信加密签名", requiredMode = Schema.RequiredMode.REQUIRED, example = "490eb57f448b87bd5f20ccef58aa4de46aa1908e") - @NotEmpty(message = "微信加密签名不能为空") - private String signature; - - @Schema(description = "时间戳", requiredMode = Schema.RequiredMode.REQUIRED, example = "1672587863") - @NotEmpty(message = "时间戳不能为空") - private String timestamp; - - @Schema(description = "随机数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1827365808") - @NotEmpty(message = "随机数不能为空") - private String nonce; - - @Schema(description = "随机字符串", requiredMode = Schema.RequiredMode.REQUIRED, example = "2721154047828672511") - @NotEmpty(message = "随机字符串不能为空") - @SuppressWarnings("SpellCheckingInspection") - private String echostr; - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/open/vo/MpOpenHandleMessageReqVO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/open/vo/MpOpenHandleMessageReqVO.java deleted file mode 100644 index 11caca0..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/open/vo/MpOpenHandleMessageReqVO.java +++ /dev/null @@ -1,37 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.open.vo; - - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import jakarta.validation.constraints.NotEmpty; - -@Schema(description = "管理后台 - 公众号处理消息 Request VO") -@Data -public class MpOpenHandleMessageReqVO { - - public static final String ENCRYPT_TYPE_AES = "aes"; - - @Schema(description = "微信加密签名", requiredMode = Schema.RequiredMode.REQUIRED, example = "490eb57f448b87bd5f20ccef58aa4de46aa1908e") - @NotEmpty(message = "微信加密签名不能为空") - private String signature; - - @Schema(description = "时间戳", requiredMode = Schema.RequiredMode.REQUIRED, example = "1672587863") - @NotEmpty(message = "时间戳不能为空") - private String timestamp; - - @Schema(description = "随机数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1827365808") - @NotEmpty(message = "随机数不能为空") - private String nonce; - - @Schema(description = "粉丝 openid", requiredMode = Schema.RequiredMode.REQUIRED, example = "oz-Jdtyn-WGm4C4I5Z-nvBMO_ZfY") - @NotEmpty(message = "粉丝 openid 不能为空") - private String openid; - - @Schema(description = "消息加密类型", example = "aes") - private String encrypt_type; - - @Schema(description = "微信签名", example = "QW5kcm9pZCBUaGUgQmFzZTY0IGlzIGEgZ2VuZXJhdGVkIHN0cmluZw==") - private String msg_signature; - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/MpStatisticsController.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/MpStatisticsController.java deleted file mode 100644 index 6c5016e..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/MpStatisticsController.java +++ /dev/null @@ -1,68 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.statistics; - -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.module.mp.controller.admin.statistics.vo.*; -import cn.iocoder.yudao.module.mp.convert.statistics.MpStatisticsConvert; -import cn.iocoder.yudao.module.mp.service.statistics.MpStatisticsService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import me.chanjar.weixin.mp.bean.datacube.WxDataCubeInterfaceResult; -import me.chanjar.weixin.mp.bean.datacube.WxDataCubeMsgResult; -import me.chanjar.weixin.mp.bean.datacube.WxDataCubeUserCumulate; -import me.chanjar.weixin.mp.bean.datacube.WxDataCubeUserSummary; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import jakarta.annotation.Resource; -import java.util.List; - -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; - -@Tag(name = "管理后台 - 公众号统计") -@RestController -@RequestMapping("/mp/statistics") -@Validated -public class MpStatisticsController { - - @Resource - private MpStatisticsService mpStatisticsService; - - @GetMapping("/user-summary") - @Operation(summary = "获得粉丝增减数据") - @PreAuthorize("@ss.hasPermission('mp:statistics:query')") - public CommonResult> getUserSummary(MpStatisticsGetReqVO getReqVO) { - List list = mpStatisticsService.getUserSummary( - getReqVO.getAccountId(), getReqVO.getDate()); - return success(MpStatisticsConvert.INSTANCE.convertList01(list)); - } - - @GetMapping("/user-cumulate") - @Operation(summary = "获得粉丝累计数据") - @PreAuthorize("@ss.hasPermission('mp:statistics:query')") - public CommonResult> getUserCumulate(MpStatisticsGetReqVO getReqVO) { - List list = mpStatisticsService.getUserCumulate( - getReqVO.getAccountId(), getReqVO.getDate()); - return success(MpStatisticsConvert.INSTANCE.convertList02(list)); - } - - @GetMapping("/upstream-message") - @Operation(summary = "获取消息发送概况数据") - @PreAuthorize("@ss.hasPermission('mp:statistics:query')") - public CommonResult> getUpstreamMessage(MpStatisticsGetReqVO getReqVO) { - List list = mpStatisticsService.getUpstreamMessage( - getReqVO.getAccountId(), getReqVO.getDate()); - return success(MpStatisticsConvert.INSTANCE.convertList03(list)); - } - - @GetMapping("/interface-summary") - @Operation(summary = "获取消息发送概况数据") - @PreAuthorize("@ss.hasPermission('mp:statistics:query')") - public CommonResult> getInterfaceSummary(MpStatisticsGetReqVO getReqVO) { - List list = mpStatisticsService.getInterfaceSummary( - getReqVO.getAccountId(), getReqVO.getDate()); - return success(MpStatisticsConvert.INSTANCE.convertList04(list)); - } -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsGetReqVO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsGetReqVO.java deleted file mode 100644 index be8bca0..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsGetReqVO.java +++ /dev/null @@ -1,25 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.statistics.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotNull; -import lombok.Data; -import org.springframework.format.annotation.DateTimeFormat; - -import java.time.LocalDateTime; - -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; - -@Schema(description = "管理后台 - 获得统计数据 Request VO") -@Data -public class MpStatisticsGetReqVO { - - @Schema(description = "公众号账号的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - @NotNull(message = "公众号账号的编号不能为空") - private Long accountId; - - @Schema(description = "查询时间范围", example = "[2022-07-01 00:00:00, 2022-07-01 23:59:59]") - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) - @NotNull(message = "查询时间范围不能为空") - private LocalDateTime[] date; - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsInterfaceSummaryRespVO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsInterfaceSummaryRespVO.java deleted file mode 100644 index c7952f4..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsInterfaceSummaryRespVO.java +++ /dev/null @@ -1,27 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.statistics.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import java.time.LocalDateTime; - -@Schema(description = "管理后台 - 某一天的接口分析数据 Response VO") -@Data -public class MpStatisticsInterfaceSummaryRespVO { - - @Schema(description = "日期", requiredMode = Schema.RequiredMode.REQUIRED) - private LocalDateTime refDate; - - @Schema(description = "通过服务器配置地址获得消息后,被动回复粉丝消息的次数", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") - private Integer callbackCount; - - @Schema(description = "上述动作的失败次数", requiredMode = Schema.RequiredMode.REQUIRED, example = "20") - private Integer failCount; - - @Schema(description = "总耗时,除以 callback_count 即为平均耗时", requiredMode = Schema.RequiredMode.REQUIRED, example = "30") - private Integer totalTimeCost; - - @Schema(description = "最大耗时", requiredMode = Schema.RequiredMode.REQUIRED, example = "40") - private Integer maxTimeCost; - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsUpstreamMessageRespVO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsUpstreamMessageRespVO.java deleted file mode 100644 index f9bba2c..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsUpstreamMessageRespVO.java +++ /dev/null @@ -1,21 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.statistics.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import java.time.LocalDateTime; - -@Schema(description = "管理后台 - 某一天的粉丝增减数据 Response VO") -@Data -public class MpStatisticsUpstreamMessageRespVO { - - @Schema(description = "日期", requiredMode = Schema.RequiredMode.REQUIRED) - private LocalDateTime refDate; - - @Schema(description = "上行发送了(向公众号发送了)消息的粉丝数", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") - private Integer messageUser; - - @Schema(description = "上行发送了消息的消息总数", requiredMode = Schema.RequiredMode.REQUIRED, example = "20") - private Integer messageCount; - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsUserCumulateRespVO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsUserCumulateRespVO.java deleted file mode 100644 index 6503c07..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsUserCumulateRespVO.java +++ /dev/null @@ -1,18 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.statistics.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import java.time.LocalDateTime; - -@Schema(description = "管理后台 - 某一天的消息发送概况数据 Response VO") -@Data -public class MpStatisticsUserCumulateRespVO { - - @Schema(description = "日期", requiredMode = Schema.RequiredMode.REQUIRED) - private LocalDateTime refDate; - - @Schema(description = "累计粉丝量", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") - private Integer cumulateUser; - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsUserSummaryRespVO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsUserSummaryRespVO.java deleted file mode 100644 index 7a5373c..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsUserSummaryRespVO.java +++ /dev/null @@ -1,24 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.statistics.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import java.time.LocalDateTime; - -@Schema(description = "管理后台 - 某一天的粉丝增减数据 Response VO") -@Data -public class MpStatisticsUserSummaryRespVO { - - @Schema(description = "日期", requiredMode = Schema.RequiredMode.REQUIRED) - private LocalDateTime refDate; - - @Schema(description = "粉丝来源", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") - private Integer userSource; - - @Schema(description = "新关注的粉丝数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") - private Integer newUser; - - @Schema(description = "取消关注的粉丝数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "20") - private Integer cancelUser; - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/MpTagController.http b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/MpTagController.http deleted file mode 100644 index a888145..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/MpTagController.http +++ /dev/null @@ -1,39 +0,0 @@ -### 请求 /mp/tag/create 接口 => 成功 -POST {{baseUrl}}/mp/tag/create -Content-Type: application/json -Authorization: Bearer {{token}} -tenant-id: {{adminTenantId}} - -{ - "accountId": "1", - "name": "测试" -} - -### 请求 /mp/tag/update 接口 => 成功 -PUT {{baseUrl}}/mp/tag/update -Content-Type: application/json -Authorization: Bearer {{token}} -tenant-id: {{adminTenantId}} - -{ - "id": "3", - "name": "测试标签啦" -} - -### 请求 /mp/tag/delete 接口 => 成功 -DELETE {{baseUrl}}/mp/tag/delete?id=3 -Content-Type: application/json -Authorization: Bearer {{token}} -tenant-id: {{adminTenantId}} - -### 请求 /mp/tag/page 接口 => 成功 -GET {{baseUrl}}/mp/tag/page?accountId=1&pageNo=1&pageSize=10 -Content-Type: application/json -Authorization: Bearer {{token}} -tenant-id: {{adminTenantId}} - -### 请求 /mp/tag/sync 接口 => 成功 -POST {{baseUrl}}/mp/tag/sync?accountId=1 -Content-Type: application/json -Authorization: Bearer {{token}} -tenant-id: {{adminTenantId}} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/MpTagController.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/MpTagController.java deleted file mode 100644 index dae24ff..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/MpTagController.java +++ /dev/null @@ -1,88 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.tag; - -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.mp.controller.admin.tag.vo.*; -import cn.iocoder.yudao.module.mp.convert.tag.MpTagConvert; -import cn.iocoder.yudao.module.mp.dal.dataobject.tag.MpTagDO; -import cn.iocoder.yudao.module.mp.service.tag.MpTagService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.tags.Tag; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import jakarta.annotation.Resource; -import jakarta.validation.Valid; -import java.util.List; - -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; - -@Tag(name = "管理后台 - 公众号标签") -@RestController -@RequestMapping("/mp/tag") -@Validated -public class MpTagController { - - @Resource - private MpTagService mpTagService; - - @PostMapping("/create") - @Operation(summary = "创建公众号标签") - @PreAuthorize("@ss.hasPermission('mp:tag:create')") - public CommonResult createTag(@Valid @RequestBody MpTagCreateReqVO createReqVO) { - return success(mpTagService.createTag(createReqVO)); - } - - @PutMapping("/update") - @Operation(summary = "更新公众号标签") - @PreAuthorize("@ss.hasPermission('mp:tag:update')") - public CommonResult updateTag(@Valid @RequestBody MpTagUpdateReqVO updateReqVO) { - mpTagService.updateTag(updateReqVO); - return success(true); - } - - @DeleteMapping("/delete") - @Operation(summary = "删除公众号标签") - @Parameter(name = "id", description = "编号", required = true) - @PreAuthorize("@ss.hasPermission('mp:tag:delete')") - public CommonResult deleteTag(@RequestParam("id") Long id) { - mpTagService.deleteTag(id); - return success(true); - } - - @GetMapping("/get") - @Operation(summary = "获取公众号标签详情") - @PreAuthorize("@ss.hasPermission('mp:tag:query')") - public CommonResult get(@RequestParam("id") Long id) { - MpTagDO mpTagDO = mpTagService.get(id); - return success(MpTagConvert.INSTANCE.convert(mpTagDO)); - } - - @GetMapping("/page") - @Operation(summary = "获取公众号标签分页") - @PreAuthorize("@ss.hasPermission('mp:tag:query')") - public CommonResult> getTagPage(MpTagPageReqVO pageReqVO) { - PageResult pageResult = mpTagService.getTagPage(pageReqVO); - return success(MpTagConvert.INSTANCE.convertPage(pageResult)); - } - - @GetMapping("/list-all-simple") - @Operation(summary = "获取公众号账号精简信息列表") - @PreAuthorize("@ss.hasPermission('mp:account:query')") - public CommonResult> getSimpleTags() { - List list = mpTagService.getTagList(); - return success(MpTagConvert.INSTANCE.convertList02(list)); - } - - @PostMapping("/sync") - @Operation(summary = "同步公众号标签") - @Parameter(name = "accountId", description = "公众号账号的编号", required = true) - @PreAuthorize("@ss.hasPermission('mp:tag:sync')") - public CommonResult syncTag(@RequestParam("accountId") Long accountId) { - mpTagService.syncTag(accountId); - return success(true); - } - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagBaseVO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagBaseVO.java deleted file mode 100644 index 68c4ae9..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagBaseVO.java +++ /dev/null @@ -1,21 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.tag.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import jakarta.validation.constraints.NotEmpty; - -/** - * 公众号标签 Base VO,提供给添加、修改、详细的子 VO 使用 - * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 - * - * @author fengdan - */ -@Data -public class MpTagBaseVO { - - @Schema(description = "标签名", requiredMode = Schema.RequiredMode.REQUIRED, example = "土豆") - @NotEmpty(message = "标签名不能为空") - private String name; - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagCreateReqVO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagCreateReqVO.java deleted file mode 100644 index 0236dbf..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagCreateReqVO.java +++ /dev/null @@ -1,20 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.tag.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; - -import jakarta.validation.constraints.NotNull; - -@Schema(description = "管理后台 - 公众号标签创建 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class MpTagCreateReqVO extends MpTagBaseVO { - - @Schema(description = "公众号账号的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") - @NotNull(message = "公众号账号的编号不能为空") - private Long accountId; - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagPageReqVO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagPageReqVO.java deleted file mode 100644 index b89cbc5..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagPageReqVO.java +++ /dev/null @@ -1,24 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.tag.vo; - -import cn.iocoder.yudao.framework.common.pojo.PageParam; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; - -import jakarta.validation.constraints.NotEmpty; - -@Schema(description = "管理后台 - 公众号标签分页 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class MpTagPageReqVO extends PageParam { - - @Schema(description = "公众号账号的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") - @NotEmpty(message = "公众号账号的编号不能为空") - private Long accountId; - - @Schema(description = "标签名,模糊匹配", example = "哈哈") - private String name; - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagRespVO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagRespVO.java deleted file mode 100644 index 0da562d..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagRespVO.java +++ /dev/null @@ -1,25 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.tag.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; - -import java.time.LocalDateTime; - -@Schema(description = "管理后台 - 公众号标签 Response VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class MpTagRespVO extends MpTagBaseVO { - - @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - private Long id; - - @Schema(description = "此标签下粉丝数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") - private Integer count; - - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) - private LocalDateTime createTime; - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagSimpleRespVO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagSimpleRespVO.java deleted file mode 100644 index ca23be1..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagSimpleRespVO.java +++ /dev/null @@ -1,19 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.tag.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -@Schema(description = "管理后台 - 公众号标签精简信息 Response VO") -@Data -public class MpTagSimpleRespVO { - - @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - private Long id; - - @Schema(description = "公众号的标签编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") - private Long tagId; - - @Schema(description = "标签名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "快乐") - private String name; - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagUpdateReqVO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagUpdateReqVO.java deleted file mode 100644 index 9710cd9..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/tag/vo/MpTagUpdateReqVO.java +++ /dev/null @@ -1,20 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.tag.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; - -import jakarta.validation.constraints.NotNull; - -@Schema(description = "管理后台 - 公众号标签更新 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class MpTagUpdateReqVO extends MpTagBaseVO { - - @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED) - @NotNull(message = "编号不能为空") - private Long id; - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/MpUserController.http b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/MpUserController.http deleted file mode 100644 index c78356a..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/MpUserController.http +++ /dev/null @@ -1,18 +0,0 @@ -### 请求 /mp/user/sync 接口 => 成功 -POST {{baseUrl}}/mp/user/sync?accountId=1 -Content-Type: application/json -Authorization: Bearer {{token}} -tenant-id: {{adminTenantId}} - -### 请求 /mp/user/update 接口 => 成功 -PUT {{baseUrl}}/mp/user/update -Content-Type: application/json -Authorization: Bearer {{token}} -tenant-id: {{adminTenantId}} - -{ - "id": "3", - "nickname": "test", - "remark": "测试备注", - "tagIds": [103, 104] -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/MpUserController.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/MpUserController.java deleted file mode 100644 index 4da1cf9..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/MpUserController.java +++ /dev/null @@ -1,65 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.user; - -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.mp.controller.admin.user.vo.MpUserPageReqVO; -import cn.iocoder.yudao.module.mp.controller.admin.user.vo.MpUserRespVO; -import cn.iocoder.yudao.module.mp.controller.admin.user.vo.MpUserUpdateReqVO; -import cn.iocoder.yudao.module.mp.convert.user.MpUserConvert; -import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO; -import cn.iocoder.yudao.module.mp.service.user.MpUserService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.tags.Tag; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import jakarta.annotation.Resource; -import jakarta.validation.Valid; - -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; - -@Tag(name = "管理后台 - 公众号粉丝") -@RestController -@RequestMapping("/mp/user") -@Validated -public class MpUserController { - - @Resource - private MpUserService mpUserService; - - @GetMapping("/page") - @Operation(summary = "获得公众号粉丝分页") - @PreAuthorize("@ss.hasPermission('mp:user:query')") - public CommonResult> getUserPage(@Valid MpUserPageReqVO pageVO) { - PageResult pageResult = mpUserService.getUserPage(pageVO); - return success(MpUserConvert.INSTANCE.convertPage(pageResult)); - } - - @GetMapping("/get") - @Operation(summary = "获得公众号粉丝") - @Parameter(name = "id", description = "编号", required = true, example = "1024") - @PreAuthorize("@ss.hasPermission('mp:user:query')") - public CommonResult getUser(@RequestParam("id") Long id) { - return success(MpUserConvert.INSTANCE.convert(mpUserService.getUser(id))); - } - - @PutMapping("/update") - @Operation(summary = "更新公众号粉丝") - @PreAuthorize("@ss.hasPermission('mp:user:update')") - public CommonResult updateUser(@Valid @RequestBody MpUserUpdateReqVO updateReqVO) { - mpUserService.updateUser(updateReqVO); - return success(true); - } - - @PostMapping("/sync") - @Operation(summary = "同步公众号粉丝") - @Parameter(name = "accountId", description = "公众号账号的编号", required = true) - @PreAuthorize("@ss.hasPermission('mp:user:sync')") - public CommonResult syncUser(@RequestParam("accountId") Long accountId) { - mpUserService.syncUser(accountId); - return success(true); - } - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/vo/MpUserPageReqVO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/vo/MpUserPageReqVO.java deleted file mode 100644 index 66f45f1..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/vo/MpUserPageReqVO.java +++ /dev/null @@ -1,30 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.user.vo; - -import cn.iocoder.yudao.framework.common.pojo.PageParam; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; - -import jakarta.validation.constraints.NotNull; - -@Schema(description = "管理后台 - 公众号粉丝分页 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class MpUserPageReqVO extends PageParam { - - @Schema(description = "公众号账号的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") - @NotNull(message = "公众号账号的编号不能为空") - private Long accountId; - - @Schema(description = "公众号粉丝标识,模糊匹配", example = "o6_bmjrPTlm6_2sgVt7hMZOPfL2M") - private String openid; - - @Schema(description = "微信生态唯一标识,模糊匹配", example = "o6_bmjrPTlm6_2sgVt7hMZOPfL2M") - private String unionId; - - @Schema(description = "公众号粉丝昵称,模糊匹配", example = "芋艿") - private String nickname; - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/vo/MpUserRespVO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/vo/MpUserRespVO.java deleted file mode 100644 index 95f919a..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/vo/MpUserRespVO.java +++ /dev/null @@ -1,55 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.user.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import java.time.LocalDateTime; -import java.util.List; - -@Schema(description = "管理后台 - 公众号粉丝 Response VO") -@Data -public class MpUserRespVO { - - @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - private Long id; - - @Schema(description = "公众号粉丝标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "o6_bmjrPTlm6_2sgVt7hMZOPfL2M") - private String openid; - - @Schema(description = "微信生态唯一标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "o6_bmjrPTlm6_2sgVt7hMZOPfL2M") - private String unionId; - - @Schema(description = "关注状态 参见 CommonStatusEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - private Integer subscribeStatus; - @Schema(description = "关注时间", requiredMode = Schema.RequiredMode.REQUIRED) - private LocalDateTime subscribeTime; - @Schema(description = "取消关注时间") - private LocalDateTime unsubscribeTime; - - @Schema(description = "昵称", example = "芋道") - private String nickname; - @Schema(description = "头像地址", example = "https://www.iocoder.cn/1.png") - private String headImageUrl; - @Schema(description = "语言", example = "zh_CN") - private String language; - @Schema(description = "国家", example = "中国") - private String country; - @Schema(description = "省份", example = "广东省") - private String province; - @Schema(description = "城市", example = "广州市") - private String city; - @Schema(description = "备注", example = "你是一个芋头嘛") - private String remark; - - @Schema(description = "标签编号数组", example = "1,2,3") - private List tagIds; - - @Schema(description = "公众号账号的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - private Long accountId; - @Schema(description = "公众号账号的 appId", requiredMode = Schema.RequiredMode.REQUIRED, example = "wx1234567890") - private String appId; - - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) - private LocalDateTime createTime; - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/vo/MpUserUpdateReqVO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/vo/MpUserUpdateReqVO.java deleted file mode 100644 index 490287e..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/vo/MpUserUpdateReqVO.java +++ /dev/null @@ -1,26 +0,0 @@ -package cn.iocoder.yudao.module.mp.controller.admin.user.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import jakarta.validation.constraints.NotNull; -import java.util.List; - -@Schema(description = "管理后台 - 公众号粉丝更新 Request VO") -@Data -public class MpUserUpdateReqVO { - - @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - @NotNull(message = "编号不能为空") - private Long id; - - @Schema(description = "昵称", example = "芋道") - private String nickname; - - @Schema(description = "备注", example = "你是一个芋头嘛") - private String remark; - - @Schema(description = "标签编号数组", example = "1,2,3") - private List tagIds; - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/package-info.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/package-info.java deleted file mode 100644 index 31a0577..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/package-info.java +++ /dev/null @@ -1,6 +0,0 @@ -/** - * 提供 RESTful API 给前端: - * 1. admin 包:提供给管理后台 yudao-ui-admin 前端项目 - * 2. app 包:提供给用户 APP yudao-ui-app 前端项目,它的 Controller 和 VO 都要添加 App 前缀,用于和管理后台进行区分 - */ -package cn.iocoder.yudao.module.mp.controller; diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/account/MpAccountConvert.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/account/MpAccountConvert.java deleted file mode 100644 index 5f8fde3..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/account/MpAccountConvert.java +++ /dev/null @@ -1,31 +0,0 @@ -package cn.iocoder.yudao.module.mp.convert.account; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountCreateReqVO; -import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountRespVO; -import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountSimpleRespVO; -import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountUpdateReqVO; -import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; -import org.mapstruct.Mapper; -import org.mapstruct.factory.Mappers; - -import java.util.List; - -@Mapper -public interface MpAccountConvert { - - MpAccountConvert INSTANCE = Mappers.getMapper(MpAccountConvert.class); - - MpAccountDO convert(MpAccountCreateReqVO bean); - - MpAccountDO convert(MpAccountUpdateReqVO bean); - - MpAccountRespVO convert(MpAccountDO bean); - - List convertList(List list); - - PageResult convertPage(PageResult page); - - List convertList02(List list); - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/material/MpMaterialConvert.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/material/MpMaterialConvert.java deleted file mode 100644 index ca4f69c..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/material/MpMaterialConvert.java +++ /dev/null @@ -1,47 +0,0 @@ -package cn.iocoder.yudao.module.mp.convert.material; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.mp.controller.admin.material.vo.MpMaterialRespVO; -import cn.iocoder.yudao.module.mp.controller.admin.material.vo.MpMaterialUploadRespVO; -import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; -import cn.iocoder.yudao.module.mp.dal.dataobject.material.MpMaterialDO; -import me.chanjar.weixin.mp.bean.material.WxMpMaterial; -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.Mappings; -import org.mapstruct.factory.Mappers; - -import java.io.File; - -@Mapper -public interface MpMaterialConvert { - - MpMaterialConvert INSTANCE = Mappers.getMapper(MpMaterialConvert.class); - - @Mappings({ - @Mapping(target = "id", ignore = true), - @Mapping(source = "account.id", target = "accountId"), - @Mapping(source = "account.appId", target = "appId"), - @Mapping(source = "name", target = "name") - }) - MpMaterialDO convert(String mediaId, String type, String url, MpAccountDO account, - String name); - - @Mappings({ - @Mapping(target = "id", ignore = true), - @Mapping(source = "account.id", target = "accountId"), - @Mapping(source = "account.appId", target = "appId"), - @Mapping(source = "name", target = "name") - }) - MpMaterialDO convert(String mediaId, String type, String url, MpAccountDO account, - String name, String title, String introduction, String mpUrl); - - MpMaterialUploadRespVO convert(MpMaterialDO bean); - - default WxMpMaterial convert(String name, File file, String title, String introduction) { - return new WxMpMaterial(name, file, title, introduction); - } - - PageResult convertPage(PageResult page); - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/menu/MpMenuConvert.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/menu/MpMenuConvert.java deleted file mode 100644 index a5ae32a..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/menu/MpMenuConvert.java +++ /dev/null @@ -1,50 +0,0 @@ -package cn.iocoder.yudao.module.mp.convert.menu; - -import cn.iocoder.yudao.module.mp.controller.admin.menu.vo.MpMenuRespVO; -import cn.iocoder.yudao.module.mp.controller.admin.menu.vo.MpMenuSaveReqVO; -import cn.iocoder.yudao.module.mp.dal.dataobject.menu.MpMenuDO; -import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO; -import me.chanjar.weixin.common.bean.menu.WxMenuButton; -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.Mappings; -import org.mapstruct.factory.Mappers; - -import java.util.List; - -@Mapper -public interface MpMenuConvert { - - MpMenuConvert INSTANCE = Mappers.getMapper(MpMenuConvert.class); - - MpMenuRespVO convert(MpMenuDO bean); - - List convertList(List list); - - @Mappings({ - @Mapping(source = "menu.appId", target = "appId"), - @Mapping(source = "menu.replyMessageType", target = "type"), - @Mapping(source = "menu.replyContent", target = "content"), - @Mapping(source = "menu.replyMediaId", target = "mediaId"), - @Mapping(source = "menu.replyThumbMediaId", target = "thumbMediaId"), - @Mapping(source = "menu.replyTitle", target = "title"), - @Mapping(source = "menu.replyDescription", target = "description"), - @Mapping(source = "menu.replyArticles", target = "articles"), - @Mapping(source = "menu.replyMusicUrl", target = "musicUrl"), - @Mapping(source = "menu.replyHqMusicUrl", target = "hqMusicUrl"), - }) - MpMessageSendOutReqBO convert(String openid, MpMenuDO menu); - - List convert(List list); - - @Mappings({ - @Mapping(source = "menuKey", target = "key"), - @Mapping(source = "children", target = "subButtons"), - @Mapping(source = "miniProgramAppId", target = "appId"), - @Mapping(source = "miniProgramPagePath", target = "pagePath"), - }) - WxMenuButton convert(MpMenuSaveReqVO.Menu bean); - - MpMenuDO convert02(MpMenuSaveReqVO.Menu menu); - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/message/MpAutoReplyConvert.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/message/MpAutoReplyConvert.java deleted file mode 100644 index c7cf890..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/message/MpAutoReplyConvert.java +++ /dev/null @@ -1,37 +0,0 @@ -package cn.iocoder.yudao.module.mp.convert.message; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.mp.controller.admin.message.vo.autoreply.MpAutoReplyCreateReqVO; -import cn.iocoder.yudao.module.mp.controller.admin.message.vo.autoreply.MpAutoReplyRespVO; -import cn.iocoder.yudao.module.mp.controller.admin.message.vo.autoreply.MpAutoReplyUpdateReqVO; -import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpAutoReplyDO; -import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO; -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.Mappings; -import org.mapstruct.factory.Mappers; - -@Mapper -public interface MpAutoReplyConvert { - - MpAutoReplyConvert INSTANCE = Mappers.getMapper(MpAutoReplyConvert.class); - - @Mappings({ - @Mapping(source = "reply.appId", target = "appId"), - @Mapping(source = "reply.responseMessageType", target = "type"), - @Mapping(source = "reply.responseContent", target = "content"), - @Mapping(source = "reply.responseMediaId", target = "mediaId"), - @Mapping(source = "reply.responseTitle", target = "title"), - @Mapping(source = "reply.responseDescription", target = "description"), - @Mapping(source = "reply.responseArticles", target = "articles"), - }) - MpMessageSendOutReqBO convert(String openid, MpAutoReplyDO reply); - - PageResult convertPage(PageResult page); - - MpAutoReplyRespVO convert(MpAutoReplyDO bean); - - MpAutoReplyDO convert(MpAutoReplyCreateReqVO bean); - - MpAutoReplyDO convert(MpAutoReplyUpdateReqVO bean); -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/message/MpMessageConvert.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/message/MpMessageConvert.java deleted file mode 100644 index 8f31a68..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/message/MpMessageConvert.java +++ /dev/null @@ -1,172 +0,0 @@ -package cn.iocoder.yudao.module.mp.convert.message; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.mp.controller.admin.message.vo.message.MpMessageRespVO; -import cn.iocoder.yudao.module.mp.controller.admin.message.vo.message.MpMessageSendReqVO; -import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; -import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO; -import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO; -import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO; -import me.chanjar.weixin.common.api.WxConsts; -import me.chanjar.weixin.mp.bean.kefu.WxMpKefuMessage; -import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; -import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; -import me.chanjar.weixin.mp.bean.message.WxMpXmlOutNewsMessage; -import me.chanjar.weixin.mp.builder.outxml.BaseBuilder; -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.Mappings; -import org.mapstruct.factory.Mappers; - -import java.util.List; - -@Mapper -public interface MpMessageConvert { - - MpMessageConvert INSTANCE = Mappers.getMapper(MpMessageConvert.class); - - MpMessageRespVO convert(MpMessageDO bean); - - List convertList(List list); - - PageResult convertPage(PageResult page); - - default MpMessageDO convert(WxMpXmlMessage wxMessage, MpAccountDO account, MpUserDO user) { - MpMessageDO message = convert(wxMessage); - if (account != null) { - message.setAccountId(account.getId()).setAppId(account.getAppId()); - } - if (user != null) { - message.setUserId(user.getId()).setOpenid(user.getOpenid()); - } - return message; - } - @Mappings(value = { - @Mapping(source = "msgType", target = "type"), - @Mapping(target = "createTime", ignore = true), - }) - MpMessageDO convert(WxMpXmlMessage bean); - - default MpMessageDO convert(MpMessageSendOutReqBO sendReqBO, MpAccountDO account, MpUserDO user) { - // 构建消息 - MpMessageDO message = new MpMessageDO(); - message.setType(sendReqBO.getType()); - switch (sendReqBO.getType()) { - case WxConsts.XmlMsgType.TEXT: // 1. 文本 - message.setContent(sendReqBO.getContent()); - break; - case WxConsts.XmlMsgType.IMAGE: // 2. 图片 - case WxConsts.XmlMsgType.VOICE: // 3. 语音 - message.setMediaId(sendReqBO.getMediaId()); - break; - case WxConsts.XmlMsgType.VIDEO: // 4. 视频 - message.setMediaId(sendReqBO.getMediaId()) - .setTitle(sendReqBO.getTitle()).setDescription(sendReqBO.getDescription()); - break; - case WxConsts.XmlMsgType.NEWS: // 5. 图文 - message.setArticles(sendReqBO.getArticles()); - case WxConsts.XmlMsgType.MUSIC: // 6. 音乐 - message.setTitle(sendReqBO.getTitle()).setDescription(sendReqBO.getDescription()) - .setMusicUrl(sendReqBO.getMusicUrl()).setHqMusicUrl(sendReqBO.getHqMusicUrl()) - .setThumbMediaId(sendReqBO.getThumbMediaId()); - break; - default: - throw new IllegalArgumentException("不支持的消息类型:" + message.getType()); - } - - // 其它字段 - if (account != null) { - message.setAccountId(account.getId()).setAppId(account.getAppId()); - } - if (user != null) { - message.setUserId(user.getId()).setOpenid(user.getOpenid()); - } - return message; - } - - default WxMpXmlOutMessage convert02(MpMessageDO message, MpAccountDO account) { - BaseBuilder builder; - // 个性化字段 - switch (message.getType()) { - case WxConsts.XmlMsgType.TEXT: - builder = WxMpXmlOutMessage.TEXT().content(message.getContent()); - break; - case WxConsts.XmlMsgType.IMAGE: - builder = WxMpXmlOutMessage.IMAGE().mediaId(message.getMediaId()); - break; - case WxConsts.XmlMsgType.VOICE: - builder = WxMpXmlOutMessage.VOICE().mediaId(message.getMediaId()); - break; - case WxConsts.XmlMsgType.VIDEO: - builder = WxMpXmlOutMessage.VIDEO().mediaId(message.getMediaId()) - .title(message.getTitle()).description(message.getDescription()); - break; - case WxConsts.XmlMsgType.NEWS: - builder = WxMpXmlOutMessage.NEWS().articles(convertList02(message.getArticles())); - break; - case WxConsts.XmlMsgType.MUSIC: - builder = WxMpXmlOutMessage.MUSIC().title(message.getTitle()).description(message.getDescription()) - .musicUrl(message.getMusicUrl()).hqMusicUrl(message.getHqMusicUrl()) - .thumbMediaId(message.getThumbMediaId()); - break; - default: - throw new IllegalArgumentException("不支持的消息类型:" + message.getType()); - } - // 通用字段 - builder.fromUser(account.getAccount()); - builder.toUser(message.getOpenid()); - return builder.build(); - } - List convertList02(List list); - - default WxMpKefuMessage convert(MpMessageSendReqVO sendReqVO, MpUserDO user) { - me.chanjar.weixin.mp.builder.kefu.BaseBuilder builder; - // 个性化字段 - switch (sendReqVO.getType()) { - case WxConsts.KefuMsgType.TEXT: - builder = WxMpKefuMessage.TEXT().content(sendReqVO.getContent()); - break; - case WxConsts.KefuMsgType.IMAGE: - builder = WxMpKefuMessage.IMAGE().mediaId(sendReqVO.getMediaId()); - break; - case WxConsts.KefuMsgType.VOICE: - builder = WxMpKefuMessage.VOICE().mediaId(sendReqVO.getMediaId()); - break; - case WxConsts.KefuMsgType.VIDEO: - builder = WxMpKefuMessage.VIDEO().mediaId(sendReqVO.getMediaId()) - .title(sendReqVO.getTitle()).description(sendReqVO.getDescription()); - break; - case WxConsts.KefuMsgType.NEWS: - builder = WxMpKefuMessage.NEWS().articles(convertList03(sendReqVO.getArticles())); - break; - case WxConsts.KefuMsgType.MUSIC: - builder = WxMpKefuMessage.MUSIC().title(sendReqVO.getTitle()).description(sendReqVO.getDescription()) - .thumbMediaId(sendReqVO.getThumbMediaId()) - .musicUrl(sendReqVO.getMusicUrl()).hqMusicUrl(sendReqVO.getHqMusicUrl()); - break; - default: - throw new IllegalArgumentException("不支持的消息类型:" + sendReqVO.getType()); - } - // 通用字段 - builder.toUser(user.getOpenid()); - return builder.build(); - } - List convertList03(List list); - - default MpMessageDO convert(WxMpKefuMessage wxMessage, MpAccountDO account, MpUserDO user) { - MpMessageDO message = convert(wxMessage); - if (account != null) { - message.setAccountId(account.getId()).setAppId(account.getAppId()); - } - if (user != null) { - message.setUserId(user.getId()).setOpenid(user.getOpenid()); - } - return message; - } - @Mappings(value = { - @Mapping(source = "msgType", target = "type"), - @Mapping(target = "createTime", ignore = true), - }) - MpMessageDO convert(WxMpKefuMessage bean); - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/statistics/MpStatisticsConvert.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/statistics/MpStatisticsConvert.java deleted file mode 100644 index 9d4d7e3..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/statistics/MpStatisticsConvert.java +++ /dev/null @@ -1,52 +0,0 @@ -package cn.iocoder.yudao.module.mp.convert.statistics; - -import cn.iocoder.yudao.module.mp.controller.admin.statistics.vo.MpStatisticsInterfaceSummaryRespVO; -import cn.iocoder.yudao.module.mp.controller.admin.statistics.vo.MpStatisticsUpstreamMessageRespVO; -import cn.iocoder.yudao.module.mp.controller.admin.statistics.vo.MpStatisticsUserCumulateRespVO; -import cn.iocoder.yudao.module.mp.controller.admin.statistics.vo.MpStatisticsUserSummaryRespVO; -import me.chanjar.weixin.mp.bean.datacube.WxDataCubeInterfaceResult; -import me.chanjar.weixin.mp.bean.datacube.WxDataCubeMsgResult; -import me.chanjar.weixin.mp.bean.datacube.WxDataCubeUserCumulate; -import me.chanjar.weixin.mp.bean.datacube.WxDataCubeUserSummary; -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.Mappings; -import org.mapstruct.Named; -import org.mapstruct.factory.Mappers; - -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.List; - -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY; - -@Mapper -public interface MpStatisticsConvert { - - MpStatisticsConvert INSTANCE = Mappers.getMapper(MpStatisticsConvert.class); - - List convertList01(List list); - - List convertList02(List list); - - List convertList03(List list); - - @Mappings({ - @Mapping(target = "refDate", expression = "java(dateFormat0(bean.getRefDate()))"), - @Mapping(source = "msgUser", target = "messageUser"), - @Mapping(source = "msgCount", target = "messageCount"), - }) - MpStatisticsUpstreamMessageRespVO convert(WxDataCubeMsgResult bean); - - List convertList04(List list); - - @Mapping(target = "refDate", expression = "java(dateFormat0(bean.getRefDate()))") - MpStatisticsInterfaceSummaryRespVO convert(WxDataCubeInterfaceResult bean); - - @Named("dateFormat0") - default LocalDateTime dateFormat0(String date) { - return LocalDate.parse(date, DateTimeFormatter.ofPattern(FORMAT_YEAR_MONTH_DAY)).atStartOfDay(); - } - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/tag/MpTagConvert.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/tag/MpTagConvert.java deleted file mode 100644 index 727ccd3..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/tag/MpTagConvert.java +++ /dev/null @@ -1,44 +0,0 @@ -package cn.iocoder.yudao.module.mp.convert.tag; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.mp.controller.admin.tag.vo.MpTagRespVO; -import cn.iocoder.yudao.module.mp.controller.admin.tag.vo.MpTagSimpleRespVO; -import cn.iocoder.yudao.module.mp.controller.admin.tag.vo.MpTagUpdateReqVO; -import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; -import cn.iocoder.yudao.module.mp.dal.dataobject.tag.MpTagDO; -import me.chanjar.weixin.mp.bean.tag.WxUserTag; -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.Mappings; -import org.mapstruct.factory.Mappers; - -import java.util.List; - -@Mapper -public interface MpTagConvert { - - MpTagConvert INSTANCE = Mappers.getMapper(MpTagConvert.class); - - WxUserTag convert(MpTagUpdateReqVO bean); - - MpTagRespVO convert(WxUserTag bean); - - List convertList(List list); - - PageResult convertPage(PageResult page); - - @Mappings({ - @Mapping(target = "id", ignore = true), - @Mapping(source = "tag.id", target = "tagId"), - @Mapping(source = "tag.name", target = "name"), - @Mapping(source = "tag.count", target = "count"), - @Mapping(source = "account.id", target = "accountId"), - @Mapping(source = "account.appId", target = "appId"), - }) - MpTagDO convert(WxUserTag tag, MpAccountDO account); - - MpTagRespVO convert(MpTagDO mpTagDO); - - List convertList02(List list); - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/user/MpUserConvert.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/user/MpUserConvert.java deleted file mode 100644 index 13ed0b3..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/user/MpUserConvert.java +++ /dev/null @@ -1,56 +0,0 @@ -package cn.iocoder.yudao.module.mp.convert.user; - -import cn.hutool.core.date.LocalDateTimeUtil; -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; -import cn.iocoder.yudao.module.mp.controller.admin.user.vo.MpUserRespVO; -import cn.iocoder.yudao.module.mp.controller.admin.user.vo.MpUserUpdateReqVO; -import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; -import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO; -import me.chanjar.weixin.mp.bean.result.WxMpUser; -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.Mappings; -import org.mapstruct.factory.Mappers; - -import java.util.List; - -@Mapper -public interface MpUserConvert { - - MpUserConvert INSTANCE = Mappers.getMapper(MpUserConvert.class); - - MpUserRespVO convert(MpUserDO bean); - - List convertList(List list); - - PageResult convertPage(PageResult page); - - @Mappings(value = { - @Mapping(source = "openId", target = "openid"), - @Mapping(source = "unionId", target = "unionId"), - @Mapping(source = "headImgUrl", target = "headImageUrl"), - @Mapping(target = "subscribeTime", ignore = true), // 单独转换 - }) - MpUserDO convert(WxMpUser wxMpUser); - - default MpUserDO convert(MpAccountDO account, WxMpUser wxMpUser) { - MpUserDO user = convert(wxMpUser); - user.setSubscribeStatus(wxMpUser.getSubscribe() ? CommonStatusEnum.ENABLE.getStatus() - : CommonStatusEnum.DISABLE.getStatus()); - user.setSubscribeTime(LocalDateTimeUtil.of(wxMpUser.getSubscribeTime() * 1000L)); - if (account != null) { - user.setAccountId(account.getId()); - user.setAppId(account.getAppId()); - } - return user; - } - - default List convertList(MpAccountDO account, List wxUsers) { - return CollectionUtils.convertList(wxUsers, wxUser -> convert(account, wxUser)); - } - - MpUserDO convert(MpUserUpdateReqVO bean); - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/account/MpAccountDO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/account/MpAccountDO.java deleted file mode 100644 index eddf87b..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/account/MpAccountDO.java +++ /dev/null @@ -1,62 +0,0 @@ -package cn.iocoder.yudao.module.mp.dal.dataobject.account; - -import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO; -import com.baomidou.mybatisplus.annotation.KeySequence; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import lombok.*; - -/** - * 公众号账号 DO - * - * @author 芋道源码 - */ -@TableName("mp_account") -@KeySequence("mp_account_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class MpAccountDO extends TenantBaseDO { - - /** - * 编号 - */ - @TableId - private Long id; - /** - * 公众号名称 - */ - private String name; - /** - * 公众号账号 - */ - private String account; - /** - * 公众号 appid - */ - private String appId; - /** - * 公众号密钥 - */ - private String appSecret; - /** - * 公众号token - */ - private String token; - /** - * 消息加解密密钥 - */ - private String aesKey; - /** - * 二维码图片 URL - */ - private String qrCodeUrl; - /** - * 备注 - */ - private String remark; - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/material/MpMaterialDO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/material/MpMaterialDO.java deleted file mode 100644 index 735235e..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/material/MpMaterialDO.java +++ /dev/null @@ -1,99 +0,0 @@ -package cn.iocoder.yudao.module.mp.dal.dataobject.material; - -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; -import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; -import com.baomidou.mybatisplus.annotation.KeySequence; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import lombok.*; -import me.chanjar.weixin.common.api.WxConsts; - -/** - * 公众号素材 DO - * - * 1. 临时素材 - * 2. 永久素材 - * - * @author 芋道源码 - */ -@TableName("mp_material") -@KeySequence("mp_material_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class MpMaterialDO extends BaseDO { - - /** - * 主键 - */ - @TableId - private Long id; - /** - * 公众号账号的编号 - * - * 关联 {@link MpAccountDO#getId()} - */ - private Long accountId; - /** - * 公众号 appId - * - * 冗余 {@link MpAccountDO#getAppId()} - */ - private String appId; - - /** - * 公众号素材 id - */ - private String mediaId; - /** - * 文件类型 - * - * 枚举 {@link WxConsts.MediaFileType} - */ - private String type; - /** - * 是否永久 - * - * true - 永久素材 - * false - 临时素材 - */ - private Boolean permanent; - /** - * 文件服务器的 URL - */ - private String url; - - /** - * 名字 - * - * 永久素材:非空 - * 临时素材:可能为空。 - * 1. 为空的情况:粉丝主动发送的图片、语音等 - * 2. 非空的情况:主动发送给粉丝的图片、语音等 - */ - private String name; - - /** - * 公众号文件 URL - * - * 只有【永久素材】使用 - */ - private String mpUrl; - - /** - * 视频素材的标题 - * - * 只有【永久素材】使用 - */ - private String title; - /** - * 视频素材的描述 - * - * 只有【永久素材】使用 - */ - private String introduction; - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/menu/MpMenuDO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/menu/MpMenuDO.java deleted file mode 100644 index 6ae8e6b..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/menu/MpMenuDO.java +++ /dev/null @@ -1,185 +0,0 @@ -package cn.iocoder.yudao.module.mp.dal.dataobject.menu; - -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; -import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; -import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO; -import com.baomidou.mybatisplus.annotation.KeySequence; -import com.baomidou.mybatisplus.annotation.TableField; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; -import me.chanjar.weixin.common.api.WxConsts; -import me.chanjar.weixin.common.api.WxConsts.MenuButtonType; - -import java.util.List; - -/** - * 公众号菜单 DO - * - * @author 芋道源码 - */ -@TableName(value = "mp_menu", autoResultMap = true) -@KeySequence("mp_menu_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class MpMenuDO extends BaseDO { - - /** - * 编号 - 顶级菜单 - */ - public static final Long ID_ROOT = 0L; - - /** - * 编号 - */ - @TableId - private Long id; - /** - * 公众号账号的编号 - * - * 关联 {@link MpAccountDO#getId()} - */ - private Long accountId; - /** - * 公众号 appId - * - * 冗余 {@link MpAccountDO#getAppId()} - */ - private String appId; - - /** - * 菜单名称 - */ - private String name; - /** - * 菜单标识 - * - * 支持多 DB 类型时,无法直接使用 key + @TableField("menuKey") 来实现转换,原因是 "menuKey" AS key 而存在报错 - */ - private String menuKey; - /** - * 父菜单编号 - */ - private Long parentId; - - // ========== 按钮操作 ========== - - /** - * 按钮类型 - * - * 枚举 {@link MenuButtonType} - */ - private String type; - - /** - * 网页链接 - * - * 粉丝点击菜单可打开链接,不超过 1024 字节 - * - * 类型为 {@link WxConsts.XmlMsgType} 的 VIEW、MINIPROGRAM - */ - private String url; - - /** - * 小程序的 appId - * - * 类型为 {@link MenuButtonType} 的 MINIPROGRAM - */ - private String miniProgramAppId; - /** - * 小程序的页面路径 - * - * 类型为 {@link MenuButtonType} 的 MINIPROGRAM - */ - private String miniProgramPagePath; - - /** - * 跳转图文的媒体编号 - */ - private String articleId; - - // ========== 消息内容 ========== - - /** - * 消息类型 - * - * 当 {@link #type} 为 CLICK、SCANCODE_WAITMSG - * - * 枚举 {@link WxConsts.XmlMsgType} 中的 TEXT、IMAGE、VOICE、VIDEO、NEWS、MUSIC - */ - private String replyMessageType; - - /** - * 回复的消息内容 - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 TEXT - */ - private String replyContent; - - /** - * 回复的媒体 id - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 IMAGE、VOICE、VIDEO - */ - private String replyMediaId; - /** - * 回复的媒体 URL - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 IMAGE、VOICE、VIDEO - */ - private String replyMediaUrl; - - /** - * 回复的标题 - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO - */ - private String replyTitle; - /** - * 回复的描述 - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO - */ - private String replyDescription; - - /** - * 回复的缩略图的媒体 id,通过素材管理中的接口上传多媒体文件,得到的 id - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC、VIDEO - */ - private String replyThumbMediaId; - /** - * 回复的缩略图的媒体 URL - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC、VIDEO - */ - private String replyThumbMediaUrl; - - /** - * 回复的图文消息数组 - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS - */ - @TableField(typeHandler = JacksonTypeHandler.class) - private List replyArticles; - - /** - * 回复的音乐链接 - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC - */ - private String replyMusicUrl; - /** - * 回复的高质量音乐链接 - * - * WIFI 环境优先使用该链接播放音乐 - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC - */ - private String replyHqMusicUrl; - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/message/MpAutoReplyDO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/message/MpAutoReplyDO.java deleted file mode 100644 index 6804e40..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/message/MpAutoReplyDO.java +++ /dev/null @@ -1,165 +0,0 @@ -package cn.iocoder.yudao.module.mp.dal.dataobject.message; - -import cn.iocoder.yudao.framework.common.util.collection.SetUtils; -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; -import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; -import cn.iocoder.yudao.module.mp.enums.message.MpAutoReplyMatchEnum; -import cn.iocoder.yudao.module.mp.enums.message.MpAutoReplyTypeEnum; -import com.baomidou.mybatisplus.annotation.KeySequence; -import com.baomidou.mybatisplus.annotation.TableField; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; -import me.chanjar.weixin.common.api.WxConsts; -import me.chanjar.weixin.common.api.WxConsts.XmlMsgType; - -import java.util.List; -import java.util.Set; - -/** - * 公众号消息自动回复 DO - * - * @author 芋道源码 - */ -@TableName(value = "mp_auto_reply", autoResultMap = true) -@KeySequence("mp_auto_reply_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class MpAutoReplyDO extends BaseDO { - - public static Set REQUEST_MESSAGE_TYPE = SetUtils.asSet(WxConsts.XmlMsgType.TEXT, WxConsts.XmlMsgType.IMAGE, - WxConsts.XmlMsgType.VOICE, WxConsts.XmlMsgType.VIDEO, WxConsts.XmlMsgType.SHORTVIDEO, - WxConsts.XmlMsgType.LOCATION, WxConsts.XmlMsgType.LINK); - - /** - * 主键 - */ - @TableId - private Long id; - /** - * 公众号账号的编号 - * - * 关联 {@link MpAccountDO#getId()} - */ - private Long accountId; - /** - * 公众号 appId - * - * 冗余 {@link MpAccountDO#getAppId()} - */ - private String appId; - - /** - * 回复类型 - * - * 枚举 {@link MpAutoReplyTypeEnum} - */ - private Integer type; - - // ==================== 请求消息 ==================== - - /** - * 请求的关键字 - * - * 当 {@link #type} 为 {@link MpAutoReplyTypeEnum#KEYWORD} - */ - private String requestKeyword; - /** - * 请求的关键字的匹配 - * - * 当 {@link #type} 为 {@link MpAutoReplyTypeEnum#KEYWORD} - * - * 枚举 {@link MpAutoReplyMatchEnum} - */ - private Integer requestMatch; - - /** - * 请求的消息类型 - * - * 当 {@link #type} 为 {@link MpAutoReplyTypeEnum#MESSAGE} - * - * 枚举 {@link XmlMsgType} 中的 {@link #REQUEST_MESSAGE_TYPE} - */ - private String requestMessageType; - - // ==================== 响应消息 ==================== - - /** - * 回复的消息类型 - * - * 枚举 {@link XmlMsgType} 中的 TEXT、IMAGE、VOICE、VIDEO、NEWS - */ - private String responseMessageType; - - /** - * 回复的消息内容 - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 TEXT - */ - private String responseContent; - - /** - * 回复的媒体 id - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 IMAGE、VOICE、VIDEO - */ - private String responseMediaId; - /** - * 回复的媒体 URL - */ - private String responseMediaUrl; - - /** - * 回复的标题 - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO - */ - private String responseTitle; - /** - * 回复的描述 - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO - */ - private String responseDescription; - - /** - * 回复的缩略图的媒体 id,通过素材管理中的接口上传多媒体文件,得到的 id - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC、VIDEO - */ - private String responseThumbMediaId; - /** - * 回复的缩略图的媒体 URL - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC、VIDEO - */ - private String responseThumbMediaUrl; - - /** - * 回复的图文消息 - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS - */ - @TableField(typeHandler = JacksonTypeHandler.class) - private List responseArticles; - - /** - * 回复的音乐链接 - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC - */ - private String responseMusicUrl; - /** - * 回复的高质量音乐链接 - * - * WIFI 环境优先使用该链接播放音乐 - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC - */ - private String responseHqMusicUrl; - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/message/MpMessageDO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/message/MpMessageDO.java deleted file mode 100644 index f2ddeca..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/message/MpMessageDO.java +++ /dev/null @@ -1,242 +0,0 @@ -package cn.iocoder.yudao.module.mp.dal.dataobject.message; - -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; -import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; -import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO; -import cn.iocoder.yudao.module.mp.enums.message.MpMessageSendFromEnum; -import com.baomidou.mybatisplus.annotation.KeySequence; -import com.baomidou.mybatisplus.annotation.TableField; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; -import jakarta.validation.constraints.NotEmpty; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; -import me.chanjar.weixin.common.api.WxConsts; -import me.chanjar.weixin.mp.builder.kefu.NewsBuilder; - -import java.io.Serializable; -import java.util.List; - -/** - * 公众号消息 DO - * - * @author 芋道源码 - */ -@TableName(value = "mp_message", autoResultMap = true) -@KeySequence("mp_message_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class MpMessageDO extends BaseDO { - - /** - * 主键 - */ - @TableId - private Long id; - /** - * 微信公众号消息 id - */ - private Long msgId; - /** - * 公众号账号的 ID - * - * 关联 {@link MpAccountDO#getId()} - */ - private Long accountId; - /** - * 公众号 appid - * - * 冗余 {@link MpAccountDO#getAppId()} - */ - private String appId; - /** - * 公众号粉丝的编号 - * - * 关联 {@link MpUserDO#getId()} - */ - private Long userId; - /** - * 公众号粉丝标志 - * - * 冗余 {@link MpUserDO#getOpenid()} - */ - private String openid; - - /** - * 消息类型 - * - * 枚举 {@link WxConsts.XmlMsgType} - */ - private String type; - /** - * 消息来源 - * - * 枚举 {@link MpMessageSendFromEnum} - */ - private Integer sendFrom; - - // ========= 普通消息内容 https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_standard_messages.html - - /** - * 消息内容 - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 TEXT - */ - private String content; - - /** - * 媒体文件的编号 - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 IMAGE、VOICE、VIDEO - */ - private String mediaId; - /** - * 媒体文件的 URL - */ - private String mediaUrl; - /** - * 语音识别后文本 - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 VOICE - */ - private String recognition; - /** - * 语音格式,如 amr,speex 等 - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 VOICE - */ - private String format; - /** - * 标题 - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO、MUSIC、LINK - */ - private String title; - /** - * 描述 - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO、MUSIC - */ - private String description; - - /** - * 缩略图的媒体 id,通过素材管理中的接口上传多媒体文件,得到的 id - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC、VIDEO - */ - private String thumbMediaId; - /** - * 缩略图的媒体 URL - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC、VIDEO - */ - private String thumbMediaUrl; - - /** - * 点击图文消息跳转链接 - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 LINK - */ - private String url; - - /** - * 地理位置维度 - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 LOCATION - */ - private Double locationX; - /** - * 地理位置经度 - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 LOCATION - */ - private Double locationY; - /** - * 地图缩放大小 - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 LOCATION - */ - private Double scale; - /** - * 详细地址 - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 LOCATION - * - * 例如说杨浦区黄兴路 221-4 号临 - */ - private String label; - - /** - * 图文消息数组 - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS - */ - @TableField(typeHandler = JacksonTypeHandler.class) - private List
articles; - - /** - * 音乐链接 - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC - */ - private String musicUrl; - /** - * 高质量音乐链接 - * - * WIFI 环境优先使用该链接播放音乐 - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC - */ - private String hqMusicUrl; - - // ========= 事件推送 https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_event_pushes.html - - /** - * 事件类型 - * - * 枚举 {@link WxConsts.EventType} - */ - private String event; - /** - * 事件 Key - * - * 1. {@link WxConsts.EventType} 的 SCAN:qrscene_ 为前缀,后面为二维码的参数值 - * 2. {@link WxConsts.EventType} 的 CLICK:与自定义菜单接口中 KEY 值对应 - */ - private String eventKey; - - /** - * 文章 - */ - @Data - public static class Article implements Serializable { - - /** - * 图文消息标题 - */ - @NotEmpty(message = "图文消息标题不能为空", groups = NewsBuilder.class) - private String title; - /** - * 图文消息描述 - */ - @NotEmpty(message = "图文消息描述不能为空", groups = NewsBuilder.class) - private String description; - /** - * 图片链接 - * - * 支持 JPG、PNG 格式,较好的效果为大图 360*200,小图 200*200 - */ - @NotEmpty(message = "图片链接不能为空", groups = NewsBuilder.class) - private String picUrl; - /** - * 点击图文消息跳转链接 - */ - @NotEmpty(message = "点击图文消息跳转链接不能为空", groups = NewsBuilder.class) - private String url; - - } - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/tag/MpTagDO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/tag/MpTagDO.java deleted file mode 100644 index 8a5b9f6..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/tag/MpTagDO.java +++ /dev/null @@ -1,58 +0,0 @@ -package cn.iocoder.yudao.module.mp.dal.dataobject.tag; - -import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; -import lombok.*; - -import com.baomidou.mybatisplus.annotation.*; -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; -import me.chanjar.weixin.mp.bean.tag.WxUserTag; - -/** - * 公众号标签 DO - * - * @author 芋道源码 - */ -@TableName("mp_tag") -@KeySequence("mp_tag_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class MpTagDO extends BaseDO { - - /** - * 主键 - */ - @TableId(type = IdType.INPUT) - private Long id; - /** - * 公众号标签 id - */ - private Long tagId; - /** - * 标签名 - */ - private String name; - /** - * 此标签下粉丝数 - * - * 冗余:{@link WxUserTag#getCount()} 字段,需要管理员点击【同步】后,更新该字段 - */ - private Integer count; - - /** - * 公众号账号的编号 - * - * 关联 {@link MpAccountDO#getId()} - */ - private Long accountId; - /** - * 公众号 appId - * - * 冗余 {@link MpAccountDO#getAppId()} - */ - private String appId; - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/user/MpUserDO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/user/MpUserDO.java deleted file mode 100644 index 418e96f..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/user/MpUserDO.java +++ /dev/null @@ -1,114 +0,0 @@ -package cn.iocoder.yudao.module.mp.dal.dataobject.user; - -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; -import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler; -import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; -import cn.iocoder.yudao.module.mp.dal.dataobject.tag.MpTagDO; -import com.baomidou.mybatisplus.annotation.KeySequence; -import com.baomidou.mybatisplus.annotation.TableField; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import lombok.*; - -import java.time.LocalDateTime; -import java.util.List; - -/** - * 微信公众号粉丝 DO - * - * @author 芋道源码 - */ -@TableName(value = "mp_user", autoResultMap = true) -@KeySequence("mp_user_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class MpUserDO extends BaseDO { - - /** - * 编号 - */ - @TableId - private Long id; - /** - * 粉丝标识 - */ - private String openid; - /** - * 微信生态唯一标识 - */ - private String unionId; - /** - * 关注状态 - * - * 枚举 {@link CommonStatusEnum} - * 1. 开启 - 已关注 - * 2. 禁用 - 取消关注 - */ - private Integer subscribeStatus; - /** - * 关注时间 - */ - private LocalDateTime subscribeTime; - /** - * 取消关注时间 - */ - private LocalDateTime unsubscribeTime; - /** - * 昵称 - * - * 注意,2021-12-27 公众号接口不再返回头像和昵称,只能通过微信公众号的网页登录获取 - */ - private String nickname; - /** - * 头像地址 - * - * 注意,2021-12-27 公众号接口不再返回头像和昵称,只能通过微信公众号的网页登录获取 - */ - private String headImageUrl; - /** - * 语言 - */ - private String language; - /** - * 国家 - */ - private String country; - /** - * 省份 - */ - private String province; - /** - * 城市 - */ - private String city; - /** - * 备注 - */ - private String remark; - /** - * 标签编号数组 - * - * 注意,对应的是 {@link MpTagDO#getTagId()} 字段 - */ - @TableField(typeHandler = LongListTypeHandler.class) - private List tagIds; - - /** - * 公众号账号的编号 - * - * 关联 {@link MpAccountDO#getId()} - */ - private Long accountId; - /** - * 公众号 appId - * - * 冗余 {@link MpAccountDO#getAppId()} - */ - private String appId; - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/account/MpAccountMapper.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/account/MpAccountMapper.java deleted file mode 100644 index 30c54fd..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/account/MpAccountMapper.java +++ /dev/null @@ -1,31 +0,0 @@ -package cn.iocoder.yudao.module.mp.dal.mysql.account; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; -import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountPageReqVO; -import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; -import org.apache.ibatis.annotations.Mapper; -import org.apache.ibatis.annotations.Select; - -import java.time.LocalDateTime; - -@Mapper -public interface MpAccountMapper extends BaseMapperX { - - default PageResult selectPage(MpAccountPageReqVO reqVO) { - return selectPage(reqVO, new LambdaQueryWrapperX() - .likeIfPresent(MpAccountDO::getName, reqVO.getName()) - .likeIfPresent(MpAccountDO::getAccount, reqVO.getAccount()) - .likeIfPresent(MpAccountDO::getAppId, reqVO.getAppId()) - .orderByDesc(MpAccountDO::getId)); - } - - default MpAccountDO selectByAppId(String appId) { - return selectOne(MpAccountDO::getAppId, appId); - } - - @Select("SELECT COUNT(*) FROM mp_account WHERE update_time > #{maxUpdateTime}") - Long selectCountByUpdateTimeGt(LocalDateTime maxUpdateTime); - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/material/MpMaterialMapper.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/material/MpMaterialMapper.java deleted file mode 100644 index ca3c6b3..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/material/MpMaterialMapper.java +++ /dev/null @@ -1,33 +0,0 @@ -package cn.iocoder.yudao.module.mp.dal.mysql.material; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; -import cn.iocoder.yudao.module.mp.controller.admin.material.vo.MpMaterialPageReqVO; -import cn.iocoder.yudao.module.mp.dal.dataobject.material.MpMaterialDO; -import org.apache.ibatis.annotations.Mapper; - -import java.util.Collection; -import java.util.List; - -@Mapper -public interface MpMaterialMapper extends BaseMapperX { - - default MpMaterialDO selectByAccountIdAndMediaId(Long accountId, String mediaId) { - return selectOne(MpMaterialDO::getAccountId, accountId, - MpMaterialDO::getMediaId, mediaId); - } - - default PageResult selectPage(MpMaterialPageReqVO pageReqVO) { - return selectPage(pageReqVO, new LambdaQueryWrapperX() - .eq(MpMaterialDO::getAccountId, pageReqVO.getAccountId()) - .eqIfPresent(MpMaterialDO::getPermanent, pageReqVO.getPermanent()) - .eqIfPresent(MpMaterialDO::getType, pageReqVO.getType()) - .orderByDesc(MpMaterialDO::getId)); - } - - default List selectListByMediaId(Collection mediaIds) { - return selectList(MpMaterialDO::getMediaId, mediaIds); - } - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/menu/MpMenuMapper.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/menu/MpMenuMapper.java deleted file mode 100644 index fccd7f2..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/menu/MpMenuMapper.java +++ /dev/null @@ -1,25 +0,0 @@ -package cn.iocoder.yudao.module.mp.dal.mysql.menu; - -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; -import cn.iocoder.yudao.module.mp.dal.dataobject.menu.MpMenuDO; -import org.apache.ibatis.annotations.Mapper; - -import java.util.List; - -@Mapper -public interface MpMenuMapper extends BaseMapperX { - - default MpMenuDO selectByAppIdAndMenuKey(String appId, String menuKey) { - return selectOne(MpMenuDO::getAppId, appId, - MpMenuDO::getMenuKey, menuKey); - } - - default List selectListByAccountId(Long accountId) { - return selectList(MpMenuDO::getAccountId, accountId); - } - - default void deleteByAccountId(Long accountId) { - delete(new LambdaQueryWrapperX().eq(MpMenuDO::getAccountId, accountId)); - } -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/message/MpAutoReplyMapper.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/message/MpAutoReplyMapper.java deleted file mode 100644 index c8260e8..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/message/MpAutoReplyMapper.java +++ /dev/null @@ -1,70 +0,0 @@ -package cn.iocoder.yudao.module.mp.dal.mysql.message; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; -import cn.iocoder.yudao.module.mp.controller.admin.message.vo.message.MpMessagePageReqVO; -import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpAutoReplyDO; -import cn.iocoder.yudao.module.mp.enums.message.MpAutoReplyMatchEnum; -import cn.iocoder.yudao.module.mp.enums.message.MpAutoReplyTypeEnum; -import org.apache.ibatis.annotations.Mapper; - -import java.util.List; - -@Mapper -public interface MpAutoReplyMapper extends BaseMapperX { - - default PageResult selectPage(MpMessagePageReqVO pageVO) { - return selectPage(pageVO, new LambdaQueryWrapperX() - .eq(MpAutoReplyDO::getAccountId, pageVO.getAccountId()) - .eqIfPresent(MpAutoReplyDO::getType, pageVO.getType())); - } - - default List selectListByAppIdAndKeywordAll(String appId, String requestKeyword) { - return selectList(new LambdaQueryWrapperX() - .eq(MpAutoReplyDO::getAppId, appId) - .eq(MpAutoReplyDO::getType, MpAutoReplyTypeEnum.KEYWORD.getType()) - .eq(MpAutoReplyDO::getRequestMatch, MpAutoReplyMatchEnum.ALL.getMatch()) - .eq(MpAutoReplyDO::getRequestKeyword, requestKeyword)); - } - - default List selectListByAppIdAndKeywordLike(String appId, String requestKeyword) { - return selectList(new LambdaQueryWrapperX() - .eq(MpAutoReplyDO::getAppId, appId) - .eq(MpAutoReplyDO::getType, MpAutoReplyTypeEnum.KEYWORD.getType()) - .eq(MpAutoReplyDO::getRequestMatch, MpAutoReplyMatchEnum.LIKE.getMatch()) - .like(MpAutoReplyDO::getRequestKeyword, requestKeyword)); - } - - default List selectListByAppIdAndMessage(String appId, String requestMessageType) { - return selectList(new LambdaQueryWrapperX() - .eq(MpAutoReplyDO::getAppId, appId) - .eq(MpAutoReplyDO::getType, MpAutoReplyTypeEnum.MESSAGE.getType()) - .eq(MpAutoReplyDO::getRequestMessageType, requestMessageType)); - } - - default List selectListByAppIdAndSubscribe(String appId) { - return selectList(new LambdaQueryWrapperX() - .eq(MpAutoReplyDO::getAppId, appId) - .eq(MpAutoReplyDO::getType, MpAutoReplyTypeEnum.SUBSCRIBE.getType())); - } - - default MpAutoReplyDO selectByAccountIdAndSubscribe(Long accountId) { - return selectOne(MpAutoReplyDO::getAccountId, accountId, - MpAutoReplyDO::getType, MpAutoReplyTypeEnum.SUBSCRIBE.getType()); - } - - default MpAutoReplyDO selectByAccountIdAndMessage(Long accountId, String requestMessageType) { - return selectOne(new LambdaQueryWrapperX() - .eq(MpAutoReplyDO::getAccountId, accountId) - .eq(MpAutoReplyDO::getType, MpAutoReplyTypeEnum.MESSAGE.getType()) - .eq(MpAutoReplyDO::getRequestMessageType, requestMessageType)); - } - - default MpAutoReplyDO selectByAccountIdAndKeyword(Long accountId, String requestKeyword) { - return selectOne(new LambdaQueryWrapperX() - .eq(MpAutoReplyDO::getAccountId, accountId) - .eq(MpAutoReplyDO::getType, MpAutoReplyTypeEnum.KEYWORD.getType()) - .eq(MpAutoReplyDO::getRequestKeyword, requestKeyword)); - } -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/message/MpMessageMapper.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/message/MpMessageMapper.java deleted file mode 100644 index 72ba566..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/message/MpMessageMapper.java +++ /dev/null @@ -1,22 +0,0 @@ -package cn.iocoder.yudao.module.mp.dal.mysql.message; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; -import cn.iocoder.yudao.module.mp.controller.admin.message.vo.message.MpMessagePageReqVO; -import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO; -import org.apache.ibatis.annotations.Mapper; - -@Mapper -public interface MpMessageMapper extends BaseMapperX { - - default PageResult selectPage(MpMessagePageReqVO reqVO) { - return selectPage(reqVO, new LambdaQueryWrapperX() - .eqIfPresent(MpMessageDO::getAccountId, reqVO.getAccountId()) - .eqIfPresent(MpMessageDO::getType, reqVO.getType()) - .eqIfPresent(MpMessageDO::getOpenid, reqVO.getOpenid()) - .betweenIfPresent(MpMessageDO::getCreateTime, reqVO.getCreateTime()) - .orderByDesc(MpMessageDO::getId)); - } - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/tag/MpTagMapper.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/tag/MpTagMapper.java deleted file mode 100644 index 760f53a..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/tag/MpTagMapper.java +++ /dev/null @@ -1,26 +0,0 @@ -package cn.iocoder.yudao.module.mp.dal.mysql.tag; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; -import cn.iocoder.yudao.module.mp.controller.admin.tag.vo.MpTagPageReqVO; -import cn.iocoder.yudao.module.mp.dal.dataobject.tag.MpTagDO; -import org.apache.ibatis.annotations.Mapper; - -import java.util.List; - -@Mapper -public interface MpTagMapper extends BaseMapperX { - - default PageResult selectPage(MpTagPageReqVO reqVO) { - return selectPage(reqVO, new LambdaQueryWrapperX() - .eqIfPresent(MpTagDO::getAccountId, reqVO.getAccountId()) - .likeIfPresent(MpTagDO::getName, reqVO.getName()) - .orderByDesc(MpTagDO::getId)); - } - - default List selectListByAccountId(Long accountId) { - return selectList(MpTagDO::getAccountId, accountId); - } - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/user/MpUserMapper.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/user/MpUserMapper.java deleted file mode 100644 index 472b357..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/user/MpUserMapper.java +++ /dev/null @@ -1,35 +0,0 @@ -package cn.iocoder.yudao.module.mp.dal.mysql.user; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; -import cn.iocoder.yudao.module.mp.controller.admin.user.vo.MpUserPageReqVO; -import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO; -import org.apache.ibatis.annotations.Mapper; - -import java.util.List; - -@Mapper -public interface MpUserMapper extends BaseMapperX { - - default PageResult selectPage(MpUserPageReqVO reqVO) { - return selectPage(reqVO, new LambdaQueryWrapperX() - .likeIfPresent(MpUserDO::getOpenid, reqVO.getOpenid()) - .likeIfPresent(MpUserDO::getNickname, reqVO.getNickname()) - .eqIfPresent(MpUserDO::getAccountId, reqVO.getAccountId()) - .orderByDesc(MpUserDO::getId)); - } - - default MpUserDO selectByAppIdAndOpenid(String appId, String openid) { - return selectFirstOne(MpUserDO::getAppId, appId, - MpUserDO::getOpenid, openid); - } - - default List selectListByAppIdAndOpenid(String appId, List openids) { - return selectList(new LambdaQueryWrapperX() - .eq(MpUserDO::getAppId, appId) - .in(MpUserDO::getOpenid, openids)); - - } - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/config/MpConfiguration.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/config/MpConfiguration.java deleted file mode 100644 index c908660..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/config/MpConfiguration.java +++ /dev/null @@ -1,54 +0,0 @@ -package cn.iocoder.yudao.module.mp.framework.mp.config; - -import cn.iocoder.yudao.module.mp.framework.mp.core.DefaultMpServiceFactory; -import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory; -import cn.iocoder.yudao.module.mp.service.handler.menu.MenuHandler; -import cn.iocoder.yudao.module.mp.service.handler.message.MessageReceiveHandler; -import cn.iocoder.yudao.module.mp.service.handler.message.MessageAutoReplyHandler; -import cn.iocoder.yudao.module.mp.service.handler.other.KfSessionHandler; -import cn.iocoder.yudao.module.mp.service.handler.other.NullHandler; -import cn.iocoder.yudao.module.mp.service.handler.other.ScanHandler; -import cn.iocoder.yudao.module.mp.service.handler.other.StoreCheckNotifyHandler; -import cn.iocoder.yudao.module.mp.service.handler.user.LocationHandler; -import cn.iocoder.yudao.module.mp.service.handler.user.SubscribeHandler; -import cn.iocoder.yudao.module.mp.service.handler.user.UnsubscribeHandler; -import com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties; -import me.chanjar.weixin.common.redis.RedisTemplateWxRedisOps; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.redis.core.StringRedisTemplate; - -/** - * 微信公众号的配置类 - * - * @author 芋道源码 - */ -@Configuration -public class MpConfiguration { - - @Bean - @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") - public RedisTemplateWxRedisOps redisTemplateWxRedisOps(StringRedisTemplate stringRedisTemplate) { - return new RedisTemplateWxRedisOps(stringRedisTemplate); - } - - @Bean - @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") - public MpServiceFactory mpServiceFactory(RedisTemplateWxRedisOps redisTemplateWxRedisOps, - WxMpProperties wxMpProperties, - MessageReceiveHandler messageReceiveHandler, - KfSessionHandler kfSessionHandler, - StoreCheckNotifyHandler storeCheckNotifyHandler, - MenuHandler menuHandler, - NullHandler nullHandler, - SubscribeHandler subscribeHandler, - UnsubscribeHandler unsubscribeHandler, - LocationHandler locationHandler, - ScanHandler scanHandler, - MessageAutoReplyHandler messageAutoReplyHandler) { - return new DefaultMpServiceFactory(redisTemplateWxRedisOps, wxMpProperties, - messageReceiveHandler, kfSessionHandler, storeCheckNotifyHandler, menuHandler, - nullHandler, subscribeHandler, unsubscribeHandler, locationHandler, scanHandler, messageAutoReplyHandler); - } - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/DefaultMpServiceFactory.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/DefaultMpServiceFactory.java deleted file mode 100644 index 414f73a..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/DefaultMpServiceFactory.java +++ /dev/null @@ -1,177 +0,0 @@ -package cn.iocoder.yudao.module.mp.framework.mp.core; - -import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; -import cn.iocoder.yudao.module.mp.service.handler.menu.MenuHandler; -import cn.iocoder.yudao.module.mp.service.handler.message.MessageReceiveHandler; -import cn.iocoder.yudao.module.mp.service.handler.message.MessageAutoReplyHandler; -import cn.iocoder.yudao.module.mp.service.handler.other.KfSessionHandler; -import cn.iocoder.yudao.module.mp.service.handler.other.NullHandler; -import cn.iocoder.yudao.module.mp.service.handler.other.ScanHandler; -import cn.iocoder.yudao.module.mp.service.handler.other.StoreCheckNotifyHandler; -import cn.iocoder.yudao.module.mp.service.handler.user.LocationHandler; -import cn.iocoder.yudao.module.mp.service.handler.user.SubscribeHandler; -import cn.iocoder.yudao.module.mp.service.handler.user.UnsubscribeHandler; -import com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties; -import com.google.common.collect.Maps; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import me.chanjar.weixin.common.api.WxConsts; -import me.chanjar.weixin.common.redis.RedisTemplateWxRedisOps; -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.WxMpRedisConfigImpl; -import me.chanjar.weixin.mp.constant.WxMpEventConstants; - -import java.util.List; -import java.util.Map; - -/** - * 默认的 {@link MpServiceFactory} 实现类 - * - * @author 芋道源码 - */ -@Slf4j -@RequiredArgsConstructor -public class DefaultMpServiceFactory implements MpServiceFactory { - - /** - * 微信 appId 与 WxMpService 的映射 - */ - private volatile Map appId2MpServices; - /** - * 公众号账号 id 与 WxMpService 的映射 - */ - private volatile Map id2MpServices; - /** - * 微信 appId 与 WxMpMessageRouter 的映射 - */ - private volatile Map mpMessageRouters; - - private final RedisTemplateWxRedisOps redisTemplateWxRedisOps; - private final WxMpProperties mpProperties; - - // ========== 各种 Handler ========== - - private final MessageReceiveHandler messageReceiveHandler; - private final KfSessionHandler kfSessionHandler; - private final StoreCheckNotifyHandler storeCheckNotifyHandler; - private final MenuHandler menuHandler; - private final NullHandler nullHandler; - private final SubscribeHandler subscribeHandler; - private final UnsubscribeHandler unsubscribeHandler; - private final LocationHandler locationHandler; - private final ScanHandler scanHandler; - private final MessageAutoReplyHandler messageAutoReplyHandler; - - @Override - public void init(List list) { - Map appId2MpServices = Maps.newHashMap(); - Map id2MpServices = Maps.newHashMap(); - Map mpMessageRouters = Maps.newHashMap(); - // 处理 list - list.forEach(account -> { - // 构建 WxMpService 对象 - WxMpService mpService = buildMpService(account); - appId2MpServices.put(account.getAppId(), mpService); - id2MpServices.put(account.getId(), mpService); - // 构建 WxMpMessageRouter 对象 - WxMpMessageRouter mpMessageRouter = buildMpMessageRouter(mpService); - mpMessageRouters.put(account.getAppId(), mpMessageRouter); - }); - - // 设置到缓存 - this.appId2MpServices = appId2MpServices; - this.id2MpServices = id2MpServices; - this.mpMessageRouters = mpMessageRouters; - } - - @Override - public WxMpService getMpService(Long id) { - return id2MpServices.get(id); - } - - @Override - public WxMpService getMpService(String appId) { - return appId2MpServices.get(appId); - } - - @Override - public WxMpMessageRouter getMpMessageRouter(String appId) { - return mpMessageRouters.get(appId); - } - - private WxMpService buildMpService(MpAccountDO account) { - // 第一步,创建 WxMpRedisConfigImpl 对象 - WxMpRedisConfigImpl configStorage = new WxMpRedisConfigImpl( - redisTemplateWxRedisOps, mpProperties.getConfigStorage().getKeyPrefix()); - configStorage.setAppId(account.getAppId()); - configStorage.setSecret(account.getAppSecret()); - configStorage.setToken(account.getToken()); - configStorage.setAesKey(account.getAesKey()); - - // 第二步,创建 WxMpService 对象 - WxMpService service = new WxMpServiceImpl(); - service.setWxMpConfigStorage(configStorage); - return service; - } - - private WxMpMessageRouter buildMpMessageRouter(WxMpService mpService) { - WxMpMessageRouter router = new WxMpMessageRouter(mpService); - // 记录所有事件的日志(异步执行) - router.rule().handler(messageReceiveHandler).next(); - - // 接收客服会话管理事件 - router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) - .event(WxMpEventConstants.CustomerService.KF_CREATE_SESSION) - .handler(kfSessionHandler).end(); - router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) - .event(WxMpEventConstants.CustomerService.KF_CLOSE_SESSION) - .handler(kfSessionHandler) - .end(); - router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) - .event(WxMpEventConstants.CustomerService.KF_SWITCH_SESSION) - .handler(kfSessionHandler).end(); - - // 门店审核事件 - router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) - .event(WxMpEventConstants.POI_CHECK_NOTIFY) - .handler(storeCheckNotifyHandler).end(); - - // 自定义菜单事件 - router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) - .event(WxConsts.MenuButtonType.CLICK).handler(menuHandler).end(); - - // 点击菜单连接事件 - router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) - .event(WxConsts.MenuButtonType.VIEW).handler(nullHandler).end(); - - // 关注事件 - router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) - .event(WxConsts.EventType.SUBSCRIBE).handler(subscribeHandler) - .end(); - - // 取消关注事件 - router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) - .event(WxConsts.EventType.UNSUBSCRIBE) - .handler(unsubscribeHandler).end(); - - // 上报地理位置事件 - router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) - .event(WxConsts.EventType.LOCATION).handler(locationHandler) - .end(); - - // 接收地理位置消息 - router.rule().async(false).msgType(WxConsts.XmlMsgType.LOCATION) - .handler(locationHandler).end(); - - // 扫码事件 - router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) - .event(WxConsts.EventType.SCAN).handler(scanHandler).end(); - - // 默认 - router.rule().async(false).handler(messageAutoReplyHandler).end(); - return router; - } - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/MpServiceFactory.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/MpServiceFactory.java deleted file mode 100644 index ce017ff..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/MpServiceFactory.java +++ /dev/null @@ -1,66 +0,0 @@ -package cn.iocoder.yudao.module.mp.framework.mp.core; - -import cn.hutool.core.lang.Assert; -import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; -import me.chanjar.weixin.mp.api.WxMpMessageRouter; -import me.chanjar.weixin.mp.api.WxMpService; - -import java.util.List; - -/** - * {@link WxMpService} 工厂接口 - * - * @author 芋道源码 - */ -public interface MpServiceFactory { - - /** - * 基于微信公众号的账号,初始化对应的 WxMpService 与 WxMpMessageRouter 实例 - * - * @param list 公众号的账号列表 - */ - void init(List list); - - /** - * 获得 id 对应的 WxMpService 实例 - * - * @param id 微信公众号的编号 - * @return WxMpService 实例 - */ - WxMpService getMpService(Long id); - - default WxMpService getRequiredMpService(Long id) { - WxMpService wxMpService = getMpService(id); - Assert.notNull(wxMpService, "找到对应 id({}) 的 WxMpService,请核实!", id); - return wxMpService; - } - - /** - * 获得 appId 对应的 WxMpService 实例 - * - * @param appId 微信公众号 appId - * @return WxMpService 实例 - */ - WxMpService getMpService(String appId); - - default WxMpService getRequiredMpService(String appId) { - WxMpService wxMpService = getMpService(appId); - Assert.notNull(wxMpService, "找到对应 appId({}) 的 WxMpService,请核实!", appId); - return wxMpService; - } - - /** - * 获得 appId 对应的 WxMpMessageRouter 实例 - * - * @param appId 微信公众号 appId - * @return WxMpMessageRouter 实例 - */ - WxMpMessageRouter getMpMessageRouter(String appId); - - default WxMpMessageRouter getRequiredMpMessageRouter(String appId) { - WxMpMessageRouter wxMpMessageRouter = getMpMessageRouter(appId); - Assert.notNull(wxMpMessageRouter, "找到对应 appId({}) 的 WxMpMessageRouter,请核实!", appId); - return wxMpMessageRouter; - } - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/context/MpContextHolder.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/context/MpContextHolder.java deleted file mode 100644 index e5c45c5..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/context/MpContextHolder.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2018-2025, lengleng All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * Neither the name of the pig4cloud.com developer nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * Author: lengleng (wangiegie@gmail.com) - */ - -package cn.iocoder.yudao.module.mp.framework.mp.core.context; - -import cn.iocoder.yudao.module.mp.controller.admin.open.vo.MpOpenHandleMessageReqVO; -import com.alibaba.ttl.TransmittableThreadLocal; -import lombok.experimental.UtilityClass; -import me.chanjar.weixin.mp.api.WxMpMessageHandler; - -/** - * 微信上下文 Context - * - * 目的:解决微信多公众号的问题,在 {@link WxMpMessageHandler} 实现类中,可以通过 {@link #getAppId()} 获取到当前的 appId - * - * @see cn.iocoder.yudao.module.mp.controller.admin.open.MpOpenController#handleMessage(String, String, MpOpenHandleMessageReqVO) - * - * @author 芋道源码 - */ -public class MpContextHolder { - - /** - * 微信公众号的 appId 上下文 - */ - private static final ThreadLocal APPID = new TransmittableThreadLocal<>(); - - public static void setAppId(String appId) { - APPID.set(appId); - } - - public static String getAppId() { - return APPID.get(); - } - - public static void clear() { - APPID.remove(); - } - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/util/MpUtils.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/util/MpUtils.java deleted file mode 100644 index 1b52f83..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/util/MpUtils.java +++ /dev/null @@ -1,167 +0,0 @@ -package cn.iocoder.yudao.module.mp.framework.mp.core.util; - -import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; -import lombok.extern.slf4j.Slf4j; -import me.chanjar.weixin.common.api.WxConsts; - -import jakarta.validation.Validator; - -/** - * 公众号工具类 - * - * @author 芋道源码 - */ -@Slf4j -public class MpUtils { - - /** - * 校验消息的格式是否符合要求 - * - * @param type 类型 - * @param message 消息 - */ - public static void validateMessage(Validator validator, String type, Object message) { - // 获得对应的校验 group - Class group; - switch (type) { - case WxConsts.XmlMsgType.TEXT: - group = TextMessageGroup.class; - break; - case WxConsts.XmlMsgType.IMAGE: - group = ImageMessageGroup.class; - break; - case WxConsts.XmlMsgType.VOICE: - group = VoiceMessageGroup.class; - break; - case WxConsts.XmlMsgType.VIDEO: - group = VideoMessageGroup.class; - break; - case WxConsts.XmlMsgType.NEWS: - group = NewsMessageGroup.class; - break; - case WxConsts.XmlMsgType.MUSIC: - group = MusicMessageGroup.class; - break; - default: - log.error("[validateMessage][未知的消息类型({})]", message); - throw new IllegalArgumentException("不支持的消息类型:" + type); - } - // 执行校验 - ValidationUtils.validate(validator, message, group); - } - - public static void validateButton(Validator validator, String type, String messageType, Object button) { - if (StrUtil.isBlank(type)) { - return; - } - // 获得对应的校验 group - Class group; - switch (type) { - case WxConsts.MenuButtonType.CLICK: - group = ClickButtonGroup.class; - validateMessage(validator, messageType, button); // 需要额外校验回复的消息格式 - break; - case WxConsts.MenuButtonType.VIEW: - group = ViewButtonGroup.class; - break; - case WxConsts.MenuButtonType.MINIPROGRAM: - group = MiniProgramButtonGroup.class; - break; - case WxConsts.MenuButtonType.SCANCODE_WAITMSG: - group = ScanCodeWaitMsgButtonGroup.class; - validateMessage(validator, messageType, button); // 需要额外校验回复的消息格式 - break; - case "article_" + WxConsts.MenuButtonType.VIEW_LIMITED: - group = ViewLimitedButtonGroup.class; - break; - case WxConsts.MenuButtonType.SCANCODE_PUSH: // 不用校验,直接 return 即可 - case WxConsts.MenuButtonType.PIC_SYSPHOTO: - case WxConsts.MenuButtonType.PIC_PHOTO_OR_ALBUM: - case WxConsts.MenuButtonType.PIC_WEIXIN: - case WxConsts.MenuButtonType.LOCATION_SELECT: - return; - default: - log.error("[validateButton][未知的按钮({})]", button); - throw new IllegalArgumentException("不支持的按钮类型:" + type); - } - // 执行校验 - ValidationUtils.validate(validator, button, group); - } - - /** - * 根据消息类型,获得对应的媒体文件类型 - * - * 注意,不会返回 WxConsts.MediaFileType.THUMB,因为该类型会有明确标注 - * - * @param messageType 消息类型 {@link WxConsts.XmlMsgType} - * @return 媒体文件类型 {@link WxConsts.MediaFileType} - */ - public static String getMediaFileType(String messageType) { - switch (messageType) { - case WxConsts.XmlMsgType.IMAGE: - return WxConsts.MediaFileType.IMAGE; - case WxConsts.XmlMsgType.VOICE: - return WxConsts.MediaFileType.VOICE; - case WxConsts.XmlMsgType.VIDEO: - return WxConsts.MediaFileType.VIDEO; - default: - return WxConsts.MediaFileType.FILE; - } - } - - /** - * Text 类型的消息,参数校验 Group - */ - public interface TextMessageGroup {} - - /** - * Image 类型的消息,参数校验 Group - */ - public interface ImageMessageGroup {} - - /** - * Voice 类型的消息,参数校验 Group - */ - public interface VoiceMessageGroup {} - - /** - * Video 类型的消息,参数校验 Group - */ - public interface VideoMessageGroup {} - - /** - * News 类型的消息,参数校验 Group - */ - public interface NewsMessageGroup {} - - /** - * Music 类型的消息,参数校验 Group - */ - public interface MusicMessageGroup {} - - /** - * Click 类型的按钮,参数校验 Group - */ - public interface ClickButtonGroup {} - - /** - * View 类型的按钮,参数校验 Group - */ - public interface ViewButtonGroup {} - - /** - * MiniProgram 类型的按钮,参数校验 Group - */ - public interface MiniProgramButtonGroup {} - - /** - * SCANCODE_WAITMSG 类型的按钮,参数校验 Group - */ - public interface ScanCodeWaitMsgButtonGroup {} - - /** - * VIEW_LIMITED 类型的按钮,参数校验 Group - */ - public interface ViewLimitedButtonGroup {} -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/package-info.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/package-info.java deleted file mode 100644 index 112ecdd..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/package-info.java +++ /dev/null @@ -1,6 +0,0 @@ -/** - * 属于 mp 模块的 framework 封装 - * - * @author 芋道源码 - */ -package cn.iocoder.yudao.module.mp.framework; diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/web/config/MpWebConfiguration.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/web/config/MpWebConfiguration.java deleted file mode 100644 index 75a2362..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/web/config/MpWebConfiguration.java +++ /dev/null @@ -1,24 +0,0 @@ -package cn.iocoder.yudao.module.mp.framework.web.config; - -import cn.iocoder.yudao.framework.swagger.config.YudaoSwaggerAutoConfiguration; -import org.springdoc.core.models.GroupedOpenApi; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -/** - * mp 模块的 web 组件的 Configuration - * - * @author 芋道源码 - */ -@Configuration(proxyBeanMethods = false) -public class MpWebConfiguration { - - /** - * mp 模块的 API 分组 - */ - @Bean - public GroupedOpenApi mpGroupedOpenApi() { - return YudaoSwaggerAutoConfiguration.buildGroupedOpenApi("mp"); - } - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/web/package-info.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/web/package-info.java deleted file mode 100644 index a103f1c..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/web/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * mp 模块的 web 配置 - */ -package cn.iocoder.yudao.module.mp.framework.web; diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/package-info.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/package-info.java deleted file mode 100644 index 5987180..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -/** - * mp 模块,我们放微信微信公众号。 - * 例如说:提供微信公众号的账号、菜单、粉丝、标签、消息、自动回复、素材、模板通知、运营数据等功能 - * - * 1. Controller URL:以 /mp/ 开头,避免和其它 Module 冲突 - * 2. DataObject 表名:以 mp_ 开头,方便在数据库中区分 - */ -package cn.iocoder.yudao.module.mp; diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/account/MpAccountService.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/account/MpAccountService.java deleted file mode 100644 index 1d7296d..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/account/MpAccountService.java +++ /dev/null @@ -1,110 +0,0 @@ -package cn.iocoder.yudao.module.mp.service.account; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountCreateReqVO; -import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountPageReqVO; -import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountUpdateReqVO; -import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; - -import jakarta.validation.Valid; -import java.util.List; - -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.ACCOUNT_NOT_EXISTS; - -/** - * 公众号账号 Service 接口 - * - * @author 芋道源码 - */ -public interface MpAccountService { - - /** - * 初始化缓存 - */ - void initLocalCache(); - - /** - * 创建公众号账号 - * - * @param createReqVO 创建信息 - * @return 编号 - */ - Long createAccount(@Valid MpAccountCreateReqVO createReqVO); - - /** - * 更新公众号账号 - * - * @param updateReqVO 更新信息 - */ - void updateAccount(@Valid MpAccountUpdateReqVO updateReqVO); - - /** - * 删除公众号账号 - * - * @param id 编号 - */ - void deleteAccount(Long id); - - /** - * 获得公众号账号 - * - * @param id 编号 - * @return 公众号账号 - */ - MpAccountDO getAccount(Long id); - - /** - * 获得公众号账号。若不存在,则抛出业务异常 - * - * @param id 编号 - * @return 公众号账号 - */ - default MpAccountDO getRequiredAccount(Long id) { - MpAccountDO account = getAccount(id); - if (account == null) { - throw exception(ACCOUNT_NOT_EXISTS); - } - return account; - } - - /** - * 从缓存中,获得公众号账号 - * - * @param appId 微信公众号 appId - * @return 公众号账号 - */ - MpAccountDO getAccountFromCache(String appId); - - /** - * 获得公众号账号分页 - * - * @param pageReqVO 分页查询 - * @return 公众号账号分页 - */ - PageResult getAccountPage(MpAccountPageReqVO pageReqVO); - - /** - * 获得公众号账号列表 - * - * @return 公众号账号列表 - */ - List getAccountList(); - - /** - * 生成公众号账号的二维码 - * - * @param id 编号 - */ - void generateAccountQrCode(Long id); - - /** - * 清空公众号账号的 API 配额 - * - * 参考文档:接口调用频次限制说明 - * - * @param id 编号 - */ - void clearAccountQuota(Long id); - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/account/MpAccountServiceImpl.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/account/MpAccountServiceImpl.java deleted file mode 100644 index 14e91b6..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/account/MpAccountServiceImpl.java +++ /dev/null @@ -1,229 +0,0 @@ -package cn.iocoder.yudao.module.mp.service.account; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.util.ObjUtil; -import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; -import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountCreateReqVO; -import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountPageReqVO; -import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountUpdateReqVO; -import cn.iocoder.yudao.module.mp.convert.account.MpAccountConvert; -import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; -import cn.iocoder.yudao.module.mp.dal.mysql.account.MpAccountMapper; -import cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants; -import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory; -import com.google.common.annotations.VisibleForTesting; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import me.chanjar.weixin.common.error.WxErrorException; -import me.chanjar.weixin.mp.api.WxMpService; -import me.chanjar.weixin.mp.bean.result.WxMpQrCodeTicket; -import org.springframework.context.annotation.Lazy; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Service; -import org.springframework.validation.annotation.Validated; - -import jakarta.annotation.PostConstruct; -import jakarta.annotation.Resource; -import java.time.LocalDateTime; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; - -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getMaxValue; -import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.USER_USERNAME_EXISTS; - -/** - * 公众号账号 Service 实现类 - * - * @author fengdan - */ -@Slf4j -@Service -@Validated -public class MpAccountServiceImpl implements MpAccountService { - - /** - * 账号缓存 - * key:账号编号 {@link MpAccountDO#getAppId()} - * - * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向 - */ - @Getter - private volatile Map accountCache; - - @Resource - private MpAccountMapper mpAccountMapper; - - @Resource - @Lazy // 延迟加载,解决循环依赖的问题 - private MpServiceFactory mpServiceFactory; - - @Override - @PostConstruct - public void initLocalCache() { - // 注意:忽略自动多租户,因为要全局初始化缓存 - TenantUtils.executeIgnore(() -> { - // 第一步:查询数据 - List accounts = Collections.emptyList(); - try { - accounts = mpAccountMapper.selectList(); - } catch (Throwable ex) { - if (!ex.getMessage().contains("doesn't exist")) { - throw ex; - } - log.error("[微信公众号 yudao-module-mp - 表结构未导入][参考 https://doc.iocoder.cn/mp/build/ 开启]"); - } - log.info("[initLocalCacheIfUpdate][缓存公众号账号,数量为:{}]", accounts.size()); - - // 第二步:构建缓存。创建或更新支付 Client - mpServiceFactory.init(accounts); - accountCache = convertMap(accounts, MpAccountDO::getAppId); - }); - } - - /** - * 通过定时任务轮询,刷新缓存 - * - * 目的:多节点部署时,通过轮询”通知“所有节点,进行刷新 - */ - @Scheduled(initialDelay = 60, fixedRate = 60, timeUnit = TimeUnit.SECONDS) - public void refreshLocalCache() { - // 注意:忽略自动多租户,因为要全局初始化缓存 - TenantUtils.executeIgnore(() -> { - // 情况一:如果缓存里没有数据,则直接刷新缓存 - if (CollUtil.isEmpty(accountCache)) { - initLocalCache(); - return; - } - - // 情况二,如果缓存里数据,则通过 updateTime 判断是否有数据变更,有变更则刷新缓存 - LocalDateTime maxTime = getMaxValue(accountCache.values(), MpAccountDO::getUpdateTime); - if (mpAccountMapper.selectCountByUpdateTimeGt(maxTime) > 0) { - initLocalCache(); - } - }); - } - - @Override - public Long createAccount(MpAccountCreateReqVO createReqVO) { - // 校验 appId 唯一 - validateAppIdUnique(null, createReqVO.getAppId()); - - // 插入 - MpAccountDO account = MpAccountConvert.INSTANCE.convert(createReqVO); - mpAccountMapper.insert(account); - - // 刷新缓存 - initLocalCache(); - return account.getId(); - } - - @Override - public void updateAccount(MpAccountUpdateReqVO updateReqVO) { - // 校验存在 - validateAccountExists(updateReqVO.getId()); - // 校验 appId 唯一 - validateAppIdUnique(updateReqVO.getId(), updateReqVO.getAppId()); - - // 更新 - MpAccountDO updateObj = MpAccountConvert.INSTANCE.convert(updateReqVO); - mpAccountMapper.updateById(updateObj); - - // 刷新缓存 - initLocalCache(); - } - - @Override - public void deleteAccount(Long id) { - // 校验存在 - validateAccountExists(id); - // 删除 - mpAccountMapper.deleteById(id); - - // 刷新缓存 - initLocalCache(); - } - - private MpAccountDO validateAccountExists(Long id) { - MpAccountDO account = mpAccountMapper.selectById(id); - if (account == null) { - throw ServiceExceptionUtil.exception(ErrorCodeConstants.ACCOUNT_NOT_EXISTS); - } - return account; - } - - @VisibleForTesting - public void validateAppIdUnique(Long id, String appId) { - // 多个租户,appId 是不能重复,否则公众号回调会无法识别 - TenantUtils.executeIgnore(() -> { - MpAccountDO account = mpAccountMapper.selectByAppId(appId); - if (account == null) { - return; - } - // 存在 account 记录的情况下 - if (id == null // 新增时,说明重复 - || ObjUtil.notEqual(id, account.getId())) { // 更新时,如果 id 不一致,说明重复 - throw exception(USER_USERNAME_EXISTS); - } - }); - } - - @Override - public MpAccountDO getAccount(Long id) { - return mpAccountMapper.selectById(id); - } - - @Override - public MpAccountDO getAccountFromCache(String appId) { - return accountCache.get(appId); - } - - @Override - public PageResult getAccountPage(MpAccountPageReqVO pageReqVO) { - return mpAccountMapper.selectPage(pageReqVO); - } - - @Override - public List getAccountList() { - return mpAccountMapper.selectList(); - } - - @Override - public void generateAccountQrCode(Long id) { - // 校验存在 - MpAccountDO account = validateAccountExists(id); - - // 生成二维码 - WxMpService mpService = mpServiceFactory.getRequiredMpService(account.getAppId()); - String qrCodeUrl; - try { - WxMpQrCodeTicket qrCodeTicket = mpService.getQrcodeService().qrCodeCreateLastTicket("default"); - qrCodeUrl = mpService.getQrcodeService().qrCodePictureUrl(qrCodeTicket.getTicket()); - } catch (WxErrorException e) { - throw exception(ErrorCodeConstants.ACCOUNT_GENERATE_QR_CODE_FAIL, e.getError().getErrorMsg()); - } - - // 保存二维码 - mpAccountMapper.updateById(new MpAccountDO().setId(id).setQrCodeUrl(qrCodeUrl)); - } - - @Override - public void clearAccountQuota(Long id) { - // 校验存在 - MpAccountDO account = validateAccountExists(id); - - // 生成二维码 - WxMpService mpService = mpServiceFactory.getRequiredMpService(account.getAppId()); - try { - mpService.clearQuota(account.getAppId()); - } catch (WxErrorException e) { - throw exception(ErrorCodeConstants.ACCOUNT_CLEAR_QUOTA_FAIL, e.getError().getErrorMsg()); - } - } - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/menu/MenuHandler.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/menu/MenuHandler.java deleted file mode 100644 index 83f9517..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/menu/MenuHandler.java +++ /dev/null @@ -1,34 +0,0 @@ -package cn.iocoder.yudao.module.mp.service.handler.menu; - -import cn.iocoder.yudao.module.mp.framework.mp.core.context.MpContextHolder; -import cn.iocoder.yudao.module.mp.service.menu.MpMenuService; -import me.chanjar.weixin.common.session.WxSessionManager; -import me.chanjar.weixin.mp.api.WxMpMessageHandler; -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 jakarta.annotation.Resource; -import java.util.Map; - -/** - * 自定义菜单的事件处理器 - * - * 逻辑:粉丝点击菜单时,触发对应的回复 - * - * @author 芋道源码 - */ -@Component -public class MenuHandler implements WxMpMessageHandler { - - @Resource - private MpMenuService mpMenuService; - - @Override - public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map context, - WxMpService weixinService, WxSessionManager sessionManager) { - return mpMenuService.reply(MpContextHolder.getAppId(), wxMessage.getEventKey(), wxMessage.getFromUser()); - } - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/message/MessageAutoReplyHandler.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/message/MessageAutoReplyHandler.java deleted file mode 100644 index 7e24a83..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/message/MessageAutoReplyHandler.java +++ /dev/null @@ -1,41 +0,0 @@ -package cn.iocoder.yudao.module.mp.service.handler.message; - -import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpAutoReplyDO; -import cn.iocoder.yudao.module.mp.framework.mp.core.context.MpContextHolder; -import cn.iocoder.yudao.module.mp.service.message.MpAutoReplyService; -import lombok.extern.slf4j.Slf4j; -import me.chanjar.weixin.common.session.WxSessionManager; -import me.chanjar.weixin.mp.api.WxMpMessageHandler; -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 jakarta.annotation.Resource; -import java.util.Map; - -/** - * 自动回复消息的事件处理器 - * - * @author 芋道源码 - */ -@Component -@Slf4j -public class MessageAutoReplyHandler implements WxMpMessageHandler { - - @Resource - private MpAutoReplyService mpAutoReplyService; - - @Override - public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map context, - WxMpService weixinService, WxSessionManager sessionManager) { - // 只处理指定类型的消息 - if (!MpAutoReplyDO.REQUEST_MESSAGE_TYPE.contains(wxMessage.getMsgType())) { - return null; - } - - // 自动回复 - return mpAutoReplyService.replyForMessage(MpContextHolder.getAppId(), wxMessage); - } - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/message/MessageReceiveHandler.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/message/MessageReceiveHandler.java deleted file mode 100644 index 1a4605b..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/message/MessageReceiveHandler.java +++ /dev/null @@ -1,36 +0,0 @@ -package cn.iocoder.yudao.module.mp.service.handler.message; - -import cn.iocoder.yudao.module.mp.framework.mp.core.context.MpContextHolder; -import cn.iocoder.yudao.module.mp.service.message.MpMessageService; -import lombok.extern.slf4j.Slf4j; -import me.chanjar.weixin.common.session.WxSessionManager; -import me.chanjar.weixin.mp.api.WxMpMessageHandler; -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 jakarta.annotation.Resource; -import java.util.Map; - -/** - * 保存微信消息的事件处理器 - * - * @author 芋道源码 - */ -@Component -@Slf4j -public class MessageReceiveHandler implements WxMpMessageHandler { - - @Resource - private MpMessageService mpMessageService; - - @Override - public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map context, - WxMpService wxMpService, WxSessionManager sessionManager) { - log.info("[handle][接收到请求消息,内容:{}]", wxMessage); - mpMessageService.receiveMessage(wxMpService, MpContextHolder.getAppId(), wxMessage); - return null; - } - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/KfSessionHandler.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/KfSessionHandler.java deleted file mode 100644 index fca7fab..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/KfSessionHandler.java +++ /dev/null @@ -1,26 +0,0 @@ -package cn.iocoder.yudao.module.mp.service.handler.other; - -import me.chanjar.weixin.common.session.WxSessionManager; -import me.chanjar.weixin.mp.api.WxMpMessageHandler; -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 芋道源码 - */ -@Component -public class KfSessionHandler implements WxMpMessageHandler { - - @Override - public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map context, - WxMpService wxMpService, WxSessionManager sessionManager) { - throw new UnsupportedOperationException("未实现该处理,请自行重写"); - } - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/NullHandler.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/NullHandler.java deleted file mode 100644 index 09b3efb..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/NullHandler.java +++ /dev/null @@ -1,24 +0,0 @@ -package cn.iocoder.yudao.module.mp.service.handler.other; - -import me.chanjar.weixin.common.session.WxSessionManager; -import me.chanjar.weixin.mp.api.WxMpMessageHandler; -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; - -/** - * 点击菜单连接的事件处理器 - */ -@Component -public class NullHandler implements WxMpMessageHandler { - - @Override - public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map context, - WxMpService wxMpService, WxSessionManager sessionManager) { - throw new UnsupportedOperationException("未实现该处理,请自行重写"); - } - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/ScanHandler.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/ScanHandler.java deleted file mode 100644 index 54a6bbc..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/ScanHandler.java +++ /dev/null @@ -1,25 +0,0 @@ -package cn.iocoder.yudao.module.mp.service.handler.other; - -import me.chanjar.weixin.common.error.WxErrorException; -import me.chanjar.weixin.common.session.WxSessionManager; -import me.chanjar.weixin.mp.api.WxMpMessageHandler; -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; - -/** - * 扫码的事件处理器 - */ -@Component -public class ScanHandler implements WxMpMessageHandler { - - @Override - public WxMpXmlOutMessage handle(WxMpXmlMessage wxMpXmlMessage, Map context, - WxMpService wxMpService, WxSessionManager wxSessionManager) throws WxErrorException { - throw new UnsupportedOperationException("未实现该处理,请自行重写"); - } - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/StoreCheckNotifyHandler.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/StoreCheckNotifyHandler.java deleted file mode 100644 index 475b8ee..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/StoreCheckNotifyHandler.java +++ /dev/null @@ -1,24 +0,0 @@ -package cn.iocoder.yudao.module.mp.service.handler.other; - -import me.chanjar.weixin.common.session.WxSessionManager; -import me.chanjar.weixin.mp.api.WxMpMessageHandler; -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; - -/** - * 门店审核事件的事件处理器 - */ -@Component -public class StoreCheckNotifyHandler implements WxMpMessageHandler { - - @Override - public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map context, - WxMpService wxMpService, WxSessionManager sessionManager) { - throw new UnsupportedOperationException("未实现该处理,请自行重写"); - } - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/package-info.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/package-info.java deleted file mode 100644 index 7fd6622..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/other/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * 本包内的 handler 都是一些不重要的,所以放在 other 其它里 - */ -package cn.iocoder.yudao.module.mp.service.handler.other; diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/user/LocationHandler.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/user/LocationHandler.java deleted file mode 100644 index ebed43f..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/user/LocationHandler.java +++ /dev/null @@ -1,49 +0,0 @@ -package cn.iocoder.yudao.module.mp.service.handler.user; - -import cn.hutool.core.util.ObjectUtil; -import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; -import cn.iocoder.yudao.module.mp.framework.mp.core.context.MpContextHolder; -import cn.iocoder.yudao.module.mp.service.message.MpAutoReplyService; -import lombok.extern.slf4j.Slf4j; -import me.chanjar.weixin.common.api.WxConsts; -import me.chanjar.weixin.common.session.WxSessionManager; -import me.chanjar.weixin.mp.api.WxMpMessageHandler; -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 jakarta.annotation.Resource; -import java.util.Map; - -/** - * 上报地理位置的事件处理器 - * - * 触发操作:打开微信公众号 -> 点击 + 号 -> 选择「语音」 - * - * 逻辑:粉丝上传地理位置时,也可以触发自动回复 - * - * @author 芋道源码 - */ -@Component -@Slf4j -public class LocationHandler implements WxMpMessageHandler { - - @Resource - private MpAutoReplyService mpAutoReplyService; - - @Override - public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map context, - WxMpService wxMpService, WxSessionManager sessionManager) { - // 防御性编程:必须是 LOCATION 消息 - if (ObjectUtil.notEqual(wxMessage.getMsgType(), WxConsts.XmlMsgType.LOCATION)) { - return null; - } - log.info("[handle][上报地理位置,纬度({})、经度({})、精度({})", wxMessage.getLatitude(), - wxMessage.getLongitude(), wxMessage.getPrecision()); - - // 自动回复 - return mpAutoReplyService.replyForMessage(MpContextHolder.getAppId(), wxMessage); - } - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/user/SubscribeHandler.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/user/SubscribeHandler.java deleted file mode 100644 index 1454083..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/user/SubscribeHandler.java +++ /dev/null @@ -1,52 +0,0 @@ -package cn.iocoder.yudao.module.mp.service.handler.user; - -import cn.iocoder.yudao.module.mp.framework.mp.core.context.MpContextHolder; -import cn.iocoder.yudao.module.mp.service.message.MpAutoReplyService; -import cn.iocoder.yudao.module.mp.service.user.MpUserService; -import lombok.extern.slf4j.Slf4j; -import me.chanjar.weixin.common.error.WxErrorException; -import me.chanjar.weixin.common.session.WxSessionManager; -import me.chanjar.weixin.mp.api.WxMpMessageHandler; -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.stereotype.Component; - -import jakarta.annotation.Resource; -import java.util.Map; - -/** - * 关注的事件处理器 - * - * @author 芋道源码 - */ -@Component -@Slf4j -public class SubscribeHandler implements WxMpMessageHandler { - - @Resource - private MpUserService mpUserService; - @Resource - private MpAutoReplyService mpAutoReplyService; - - @Override - public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map context, - WxMpService weixinService, WxSessionManager sessionManager) throws WxErrorException { - // 第一步,从公众号平台,获取粉丝信息 - log.info("[handle][粉丝({}) 关注]", wxMessage.getFromUser()); - WxMpUser wxMpUser = null; - try { - wxMpUser = weixinService.getUserService().userInfo(wxMessage.getFromUser()); - } catch (WxErrorException e) { - log.error("[handle][粉丝({})] 获取粉丝信息失败!", wxMessage.getFromUser(), e); - } - - // 第二步,保存粉丝信息 - mpUserService.saveUser(MpContextHolder.getAppId(), wxMpUser); - - // 第三步,回复关注的欢迎语 - return mpAutoReplyService.replyForSubscribe(MpContextHolder.getAppId(), wxMessage); - } - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/user/UnsubscribeHandler.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/user/UnsubscribeHandler.java deleted file mode 100644 index e1afb1e..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/handler/user/UnsubscribeHandler.java +++ /dev/null @@ -1,39 +0,0 @@ -package cn.iocoder.yudao.module.mp.service.handler.user; - -import cn.iocoder.yudao.module.mp.framework.mp.core.context.MpContextHolder; -import cn.iocoder.yudao.module.mp.service.user.MpUserService; -import lombok.extern.slf4j.Slf4j; -import me.chanjar.weixin.common.session.WxSessionManager; -import me.chanjar.weixin.mp.api.WxMpMessageHandler; -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.context.annotation.Lazy; -import org.springframework.stereotype.Component; - -import jakarta.annotation.Resource; -import java.util.Map; - -/** - * 取消关注的事件处理器 - * - * @author 芋道源码 - */ -@Component -@Slf4j -public class UnsubscribeHandler implements WxMpMessageHandler { - - @Resource - @Lazy // 延迟加载,解决循环依赖的问题 - private MpUserService mpUserService; - - @Override - public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, - Map context, WxMpService wxMpService, - WxSessionManager sessionManager) { - log.info("[handle][粉丝({}) 取消关注]", wxMessage.getFromUser()); - mpUserService.updateUserUnsubscribe(MpContextHolder.getAppId(), wxMessage.getFromUser()); - return null; - } - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/material/MpMaterialService.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/material/MpMaterialService.java deleted file mode 100644 index a6a0041..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/material/MpMaterialService.java +++ /dev/null @@ -1,84 +0,0 @@ -package cn.iocoder.yudao.module.mp.service.material; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.mp.controller.admin.material.vo.MpMaterialPageReqVO; -import cn.iocoder.yudao.module.mp.controller.admin.material.vo.MpMaterialUploadNewsImageReqVO; -import cn.iocoder.yudao.module.mp.controller.admin.material.vo.MpMaterialUploadPermanentReqVO; -import cn.iocoder.yudao.module.mp.controller.admin.material.vo.MpMaterialUploadTemporaryReqVO; -import cn.iocoder.yudao.module.mp.dal.dataobject.material.MpMaterialDO; -import me.chanjar.weixin.common.api.WxConsts; - -import jakarta.validation.Valid; -import java.io.IOException; -import java.util.Collection; -import java.util.List; - -/** - * 公众号素材 Service 接口 - * - * @author 芋道源码 - */ -public interface MpMaterialService { - - /** - * 获得素材的 URL - * - * 该 URL 来自我们自己的文件服务器存储的 URL,不是公众号存储的 URL - * - * @param accountId 公众号账号编号 - * @param mediaId 公众号素材 id - * @param type 文件类型 {@link WxConsts.MediaFileType} - * @return 素材的 URL - */ - String downloadMaterialUrl(Long accountId, String mediaId, String type); - - /** - * 上传临时素材 - * - * @param reqVO 请求 - * @return 素材 - * @throws IOException 文件操作发生异常 - */ - MpMaterialDO uploadTemporaryMaterial(@Valid MpMaterialUploadTemporaryReqVO reqVO) throws IOException; - - /** - * 上传永久素材 - * - * @param reqVO 请求 - * @return 素材 - * @throws IOException 文件操作发生异常 - */ - MpMaterialDO uploadPermanentMaterial(@Valid MpMaterialUploadPermanentReqVO reqVO) throws IOException; - - /** - * 上传图文内容中的图片 - * - * @param reqVO 上传请求 - * @return 图片地址 - */ - String uploadNewsImage(MpMaterialUploadNewsImageReqVO reqVO) throws IOException; - - /** - * 获得素材分页 - * - * @param pageReqVO 分页请求 - * @return 素材分页 - */ - PageResult getMaterialPage(MpMaterialPageReqVO pageReqVO); - - /** - * 获得素材列表 - * - * @param mediaIds 素材 mediaId 列表 - * @return 素材列表 - */ - List getMaterialListByMediaId(Collection mediaIds); - - /** - * 删除素材 - * - * @param id 编号 - */ - void deleteMaterial(Long id); - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/material/MpMaterialServiceImpl.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/material/MpMaterialServiceImpl.java deleted file mode 100644 index 7241f88..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/material/MpMaterialServiceImpl.java +++ /dev/null @@ -1,224 +0,0 @@ -package cn.iocoder.yudao.module.mp.service.material; - -import cn.hutool.core.io.FileTypeUtil; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.util.ObjUtil; -import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.infra.api.file.FileApi; -import cn.iocoder.yudao.module.mp.controller.admin.material.vo.MpMaterialPageReqVO; -import cn.iocoder.yudao.module.mp.controller.admin.material.vo.MpMaterialUploadNewsImageReqVO; -import cn.iocoder.yudao.module.mp.controller.admin.material.vo.MpMaterialUploadPermanentReqVO; -import cn.iocoder.yudao.module.mp.controller.admin.material.vo.MpMaterialUploadTemporaryReqVO; -import cn.iocoder.yudao.module.mp.convert.material.MpMaterialConvert; -import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; -import cn.iocoder.yudao.module.mp.dal.dataobject.material.MpMaterialDO; -import cn.iocoder.yudao.module.mp.dal.mysql.material.MpMaterialMapper; -import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory; -import cn.iocoder.yudao.module.mp.service.account.MpAccountService; -import lombok.extern.slf4j.Slf4j; -import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; -import me.chanjar.weixin.common.error.WxErrorException; -import me.chanjar.weixin.mp.api.WxMpService; -import me.chanjar.weixin.mp.bean.material.WxMpMaterialUploadResult; -import org.springframework.context.annotation.Lazy; -import org.springframework.stereotype.Service; -import org.springframework.validation.annotation.Validated; - -import jakarta.annotation.Resource; -import java.io.File; -import java.io.IOException; -import java.util.Collection; -import java.util.List; - -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.*; - -/** - * 公众号素材 Service 接口 - * - * @author 芋道源码 - */ -@Service -@Validated -@Slf4j -public class MpMaterialServiceImpl implements MpMaterialService { - - @Resource - private MpMaterialMapper mpMaterialMapper; - - @Resource - private FileApi fileApi; - - @Resource - @Lazy // 延迟加载,解决循环依赖的问题 - private MpAccountService mpAccountService; - - @Resource - @Lazy // 延迟加载,解决循环依赖的问题 - private MpServiceFactory mpServiceFactory; - - @Override - public String downloadMaterialUrl(Long accountId, String mediaId, String type) { - // 第一步,直接从数据库查询。如果已经下载,直接返回 - MpMaterialDO material = mpMaterialMapper.selectByAccountIdAndMediaId(accountId, mediaId); - if (material != null) { - return material.getUrl(); - } - - // 第二步,尝试从临时素材中下载 - String url = downloadMedia(accountId, mediaId); - if (url == null) { - return null; - } - MpAccountDO account = mpAccountService.getRequiredAccount(accountId); - material = MpMaterialConvert.INSTANCE.convert(mediaId, type, url, account, null) - .setPermanent(false); - mpMaterialMapper.insert(material); - - // 不考虑下载永久素材,因为上传的时候已经保存 - return url; - } - - @Override - public MpMaterialDO uploadTemporaryMaterial(MpMaterialUploadTemporaryReqVO reqVO) throws IOException { - WxMpService mpService = mpServiceFactory.getRequiredMpService(reqVO.getAccountId()); - // 第一步,上传到公众号 - File file = null; - WxMediaUploadResult result; - String mediaId; - String url; - try { - // 写入到临时文件 - file = FileUtil.newFile(FileUtil.getTmpDirPath() + reqVO.getFile().getOriginalFilename()); - reqVO.getFile().transferTo(file); - // 上传到公众号 - result = mpService.getMaterialService().mediaUpload(reqVO.getType(), file); - // 上传到文件服务 - mediaId = ObjUtil.defaultIfNull(result.getMediaId(), result.getThumbMediaId()); - url = uploadFile(mediaId, file); - } catch (WxErrorException e) { - throw exception(MATERIAL_UPLOAD_FAIL, e.getError().getErrorMsg()); - } finally { - FileUtil.del(file); - } - - // 第二步,存储到数据库 - MpAccountDO account = mpAccountService.getRequiredAccount(reqVO.getAccountId()); - MpMaterialDO material = MpMaterialConvert.INSTANCE.convert(mediaId, reqVO.getType(), url, account, - reqVO.getFile().getName()).setPermanent(false); - mpMaterialMapper.insert(material); - return material; - } - - @Override - public MpMaterialDO uploadPermanentMaterial(MpMaterialUploadPermanentReqVO reqVO) throws IOException { - WxMpService mpService = mpServiceFactory.getRequiredMpService(reqVO.getAccountId()); - // 第一步,上传到公众号 - String name = StrUtil.blankToDefault(reqVO.getName(), reqVO.getFile().getName()); - File file = null; - WxMpMaterialUploadResult result; - String mediaId; - String url; - try { - // 写入到临时文件 - file = FileUtil.newFile(FileUtil.getTmpDirPath() + reqVO.getFile().getOriginalFilename()); - reqVO.getFile().transferTo(file); - // 上传到公众号 - result = mpService.getMaterialService().materialFileUpload(reqVO.getType(), - MpMaterialConvert.INSTANCE.convert(name, file, reqVO.getTitle(), reqVO.getIntroduction())); - // 上传到文件服务 - mediaId = ObjUtil.defaultIfNull(result.getMediaId(), result.getMediaId()); - url = uploadFile(mediaId, file); - } catch (WxErrorException e) { - throw exception(MATERIAL_UPLOAD_FAIL, e.getError().getErrorMsg()); - } finally { - FileUtil.del(file); - } - - // 第二步,存储到数据库 - MpAccountDO account = mpAccountService.getRequiredAccount(reqVO.getAccountId()); - MpMaterialDO material = MpMaterialConvert.INSTANCE.convert(mediaId, reqVO.getType(), url, account, - name, reqVO.getTitle(), reqVO.getIntroduction(), result.getUrl()).setPermanent(true); - mpMaterialMapper.insert(material); - return material; - } - - @Override - public String uploadNewsImage(MpMaterialUploadNewsImageReqVO reqVO) throws IOException { - WxMpService mpService = mpServiceFactory.getRequiredMpService(reqVO.getAccountId()); - File file = null; - try { - // 写入到临时文件 - file = FileUtil.newFile(FileUtil.getTmpDirPath() + reqVO.getFile().getOriginalFilename()); - reqVO.getFile().transferTo(file); - // 上传到公众号 - return mpService.getMaterialService().mediaImgUpload(file).getUrl(); - } catch (WxErrorException e) { - throw exception(MATERIAL_IMAGE_UPLOAD_FAIL, e.getError().getErrorMsg()); - } finally { - FileUtil.del(file); - } - } - - @Override - public PageResult getMaterialPage(MpMaterialPageReqVO pageReqVO) { - return mpMaterialMapper.selectPage(pageReqVO); - } - - @Override - public List getMaterialListByMediaId(Collection mediaIds) { - return mpMaterialMapper.selectListByMediaId(mediaIds); - } - - @Override - public void deleteMaterial(Long id) { - MpMaterialDO material = mpMaterialMapper.selectById(id); - if (material == null) { - throw exception(MATERIAL_NOT_EXISTS); - } - - // 第一步,从公众号删除 - if (material.getPermanent()) { - WxMpService mpService = mpServiceFactory.getRequiredMpService(material.getAppId()); - try { - mpService.getMaterialService().materialDelete(material.getMediaId()); - } catch (WxErrorException e) { - throw exception(MATERIAL_DELETE_FAIL, e.getError().getErrorMsg()); - } - } - - // 第二步,从数据库中删除 - mpMaterialMapper.deleteById(id); - } - - /** - * 下载微信媒体文件的内容,并上传到文件服务 - * - * 为什么要下载?媒体文件在微信后台保存时间为 3 天,即 3 天后 media_id 失效。 - * - * @param accountId 公众号账号的编号 - * @param mediaId 媒体文件编号 - * @return 上传后的 URL - */ - public String downloadMedia(Long accountId, String mediaId) { - WxMpService mpService = mpServiceFactory.getMpService(accountId); - for (int i = 0; i < 3; i++) { - try { - // 第一步,从公众号下载媒体文件 - File file = mpService.getMaterialService().mediaDownload(mediaId); - // 第二步,上传到文件服务 - return uploadFile(mediaId, file); - } catch (WxErrorException e) { - log.error("[mediaDownload][media({}) 第 ({}) 次下载失败]", mediaId, i); - } - } - return null; - } - - private String uploadFile(String mediaId, File file) { - String path = mediaId + "." + FileTypeUtil.getType(file); - return fileApi.createFile(FileUtil.readBytes(file), path); - } - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/menu/MpMenuService.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/menu/MpMenuService.java deleted file mode 100644 index 0b2fc45..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/menu/MpMenuService.java +++ /dev/null @@ -1,49 +0,0 @@ -package cn.iocoder.yudao.module.mp.service.menu; - -import cn.iocoder.yudao.module.mp.controller.admin.menu.vo.MpMenuSaveReqVO; -import cn.iocoder.yudao.module.mp.dal.dataobject.menu.MpMenuDO; -import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; - -import jakarta.validation.Valid; -import java.util.List; - -/** - * 公众号菜单 Service 接口 - * - * @author 芋道源码 - */ -public interface MpMenuService { - - /** - * 保存公众号菜单 - * - * @param createReqVO 创建信息 - */ - void saveMenu(@Valid MpMenuSaveReqVO createReqVO); - - /** - * 删除公众号菜单 - * - * @param accountId 公众号账号的编号 - */ - void deleteMenuByAccountId(Long accountId); - - /** - * 粉丝点击菜单按钮时,回复对应的消息 - * - * @param appId 公众号 AppId - * @param key 菜单按钮的标识 - * @param openid 粉丝的 openid - * @return 消息 - */ - WxMpXmlOutMessage reply(String appId, String key, String openid); - - /** - * 获得公众号菜单列表 - * - * @param accountId 公众号账号的编号 - * @return 公众号菜单列表 - */ - List getMenuListByAccountId(Long accountId); - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/menu/MpMenuServiceImpl.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/menu/MpMenuServiceImpl.java deleted file mode 100644 index 1afe47c..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/menu/MpMenuServiceImpl.java +++ /dev/null @@ -1,171 +0,0 @@ -package cn.iocoder.yudao.module.mp.service.menu; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.module.mp.controller.admin.menu.vo.MpMenuSaveReqVO; -import cn.iocoder.yudao.module.mp.convert.menu.MpMenuConvert; -import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; -import cn.iocoder.yudao.module.mp.dal.dataobject.menu.MpMenuDO; -import cn.iocoder.yudao.module.mp.dal.mysql.menu.MpMenuMapper; -import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory; -import cn.iocoder.yudao.module.mp.framework.mp.core.util.MpUtils; -import cn.iocoder.yudao.module.mp.service.account.MpAccountService; -import cn.iocoder.yudao.module.mp.service.message.MpMessageService; -import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO; -import lombok.extern.slf4j.Slf4j; -import me.chanjar.weixin.common.bean.menu.WxMenu; -import me.chanjar.weixin.common.error.WxErrorException; -import me.chanjar.weixin.mp.api.WxMpService; -import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; -import org.springframework.context.annotation.Lazy; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.validation.annotation.Validated; - -import jakarta.annotation.Resource; -import jakarta.validation.Validator; -import java.util.List; - -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.MENU_DELETE_FAIL; -import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.MENU_SAVE_FAIL; - -/** - * 公众号菜单 Service 实现类 - * - * @author 芋道源码 - */ -@Service -@Validated -@Slf4j -public class MpMenuServiceImpl implements MpMenuService { - - @Resource - private MpMessageService mpMessageService; - @Resource - @Lazy // 延迟加载,避免循环引用报错 - private MpAccountService mpAccountService; - - @Resource - @Lazy // 延迟加载,避免循环引用报错 - private MpServiceFactory mpServiceFactory; - - @Resource - private Validator validator; - - @Resource - private MpMenuMapper mpMenuMapper; - - @Override - @Transactional(rollbackFor = Exception.class) - public void saveMenu(MpMenuSaveReqVO createReqVO) { - MpAccountDO account = mpAccountService.getRequiredAccount(createReqVO.getAccountId()); - WxMpService mpService = mpServiceFactory.getRequiredMpService(createReqVO.getAccountId()); - - // 参数校验 - createReqVO.getMenus().forEach(this::validateMenu); - - // 第一步,同步公众号 - WxMenu wxMenu = new WxMenu(); - wxMenu.setButtons(MpMenuConvert.INSTANCE.convert(createReqVO.getMenus())); - try { - mpService.getMenuService().menuCreate(wxMenu); - } catch (WxErrorException e) { - throw exception(MENU_SAVE_FAIL, e.getError().getErrorMsg()); - } - - // 第二步,存储到数据库 - mpMenuMapper.deleteByAccountId(createReqVO.getAccountId()); - createReqVO.getMenus().forEach(menu -> { - // 先保存顶级菜单 - MpMenuDO menuDO = createMenu(menu, null, account); - // 再保存子菜单 - if (CollUtil.isEmpty(menu.getChildren())) { - return; - } - menu.getChildren().forEach(childMenu -> createMenu(childMenu, menuDO, account)); - }); - } - - /** - * 校验菜单的格式是否正确 - * - * @param menu 菜单 - */ - private void validateMenu(MpMenuSaveReqVO.Menu menu) { - MpUtils.validateButton(validator, menu.getType(), menu.getReplyMessageType(), menu); - // 子菜单 - if (CollUtil.isEmpty(menu.getChildren())) { - return; - } - menu.getChildren().forEach(this::validateMenu); - } - - /** - * 创建菜单,并存储到数据库 - * - * @param wxMenu 菜单信息 - * @param parentMenu 父菜单 - * @param account 公众号账号 - * @return 创建后的菜单 - */ - private MpMenuDO createMenu(MpMenuSaveReqVO.Menu wxMenu, MpMenuDO parentMenu, MpAccountDO account) { - // 创建菜单 - MpMenuDO menu = CollUtil.isNotEmpty(wxMenu.getChildren()) - ? new MpMenuDO().setName(wxMenu.getName()) - : MpMenuConvert.INSTANCE.convert02(wxMenu); - // 设置菜单的公众号账号信息 - if (account != null) { - menu.setAccountId(account.getId()).setAppId(account.getAppId()); - } - // 设置父编号 - if (parentMenu != null) { - menu.setParentId(parentMenu.getId()); - } else { - menu.setParentId(MpMenuDO.ID_ROOT); - } - - // 插入到数据库 - mpMenuMapper.insert(menu); - return menu; - } - - @Override - public void deleteMenuByAccountId(Long accountId) { - WxMpService mpService = mpServiceFactory.getRequiredMpService(accountId); - // 第一步,同步公众号 - try { - mpService.getMenuService().menuDelete(); - } catch (WxErrorException e) { - throw exception(MENU_DELETE_FAIL, e.getError().getErrorMsg()); - } - - // 第二步,存储到数据库 - mpMenuMapper.deleteByAccountId(accountId); - } - - @Override - public WxMpXmlOutMessage reply(String appId, String key, String openid) { - // 第一步,获得菜单 - MpMenuDO menu = mpMenuMapper.selectByAppIdAndMenuKey(appId, key); - if (menu == null) { - log.error("[reply][appId({}) key({}) 找不到对应的菜单]", appId, key); - return null; - } - // 按钮必须要有消息类型,不然后续无法回复消息 - if (StrUtil.isEmpty(menu.getReplyMessageType())) { - log.error("[reply][menu({}) 不存在对应的消息类型]", menu); - return null; - } - - // 第二步,回复消息 - MpMessageSendOutReqBO sendReqBO = MpMenuConvert.INSTANCE.convert(openid, menu); - return mpMessageService.sendOutMessage(sendReqBO); - } - - @Override - public List getMenuListByAccountId(Long accountId) { - return mpMenuMapper.selectListByAccountId(accountId); - } - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpAutoReplyService.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpAutoReplyService.java deleted file mode 100644 index fc125d0..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpAutoReplyService.java +++ /dev/null @@ -1,75 +0,0 @@ -package cn.iocoder.yudao.module.mp.service.message; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.mp.controller.admin.message.vo.autoreply.MpAutoReplyCreateReqVO; -import cn.iocoder.yudao.module.mp.controller.admin.message.vo.autoreply.MpAutoReplyUpdateReqVO; -import cn.iocoder.yudao.module.mp.controller.admin.message.vo.message.MpMessagePageReqVO; -import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpAutoReplyDO; -import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; -import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; - -/** - * 公众号的自动回复 Service 接口 - * - * @author 芋道源码 - */ -public interface MpAutoReplyService { - - /** - * 获得公众号自动回复分页 - * - * @param pageVO 分页请求 - * @return 自动回复分页结果 - */ - PageResult getAutoReplyPage(MpMessagePageReqVO pageVO); - - /** - * 获得公众号自动回复 - * - * @param id 编号 - * @return 自动回复 - */ - MpAutoReplyDO getAutoReply(Long id); - - - /** - * 创建公众号自动回复 - * - * @param createReqVO 创建请求 - * @return 自动回复的编号 - */ - Long createAutoReply(MpAutoReplyCreateReqVO createReqVO); - - /** - * 更新公众号自动回复 - * - * @param updateReqVO 更新请求 - */ - void updateAutoReply(MpAutoReplyUpdateReqVO updateReqVO); - - /** - * 删除公众号自动回复 - * - * @param id 自动回复的编号 - */ - void deleteAutoReply(Long id); - - /** - * 当收到消息时,自动回复 - * - * @param appId 微信公众号 appId - * @param wxMessage 消息 - * @return 回复的消息 - */ - WxMpXmlOutMessage replyForMessage(String appId, WxMpXmlMessage wxMessage); - - /** - * 当粉丝关注时,自动回复 - * - * @param appId 微信公众号 appId - * @param wxMessage 消息 - * @return 回复的消息 - */ - WxMpXmlOutMessage replyForSubscribe(String appId, WxMpXmlMessage wxMessage); - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpAutoReplyServiceImpl.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpAutoReplyServiceImpl.java deleted file mode 100644 index 5341e81..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpAutoReplyServiceImpl.java +++ /dev/null @@ -1,202 +0,0 @@ -package cn.iocoder.yudao.module.mp.service.message; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.lang.Assert; -import cn.hutool.core.util.ObjUtil; -import cn.iocoder.yudao.framework.common.exception.ErrorCode; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.mp.controller.admin.message.vo.autoreply.MpAutoReplyCreateReqVO; -import cn.iocoder.yudao.module.mp.controller.admin.message.vo.autoreply.MpAutoReplyUpdateReqVO; -import cn.iocoder.yudao.module.mp.controller.admin.message.vo.message.MpMessagePageReqVO; -import cn.iocoder.yudao.module.mp.convert.message.MpAutoReplyConvert; -import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; -import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpAutoReplyDO; -import cn.iocoder.yudao.module.mp.dal.mysql.message.MpAutoReplyMapper; -import cn.iocoder.yudao.module.mp.enums.message.MpAutoReplyTypeEnum; -import cn.iocoder.yudao.module.mp.framework.mp.core.util.MpUtils; -import cn.iocoder.yudao.module.mp.service.account.MpAccountService; -import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO; -import me.chanjar.weixin.common.api.WxConsts; -import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; -import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; -import org.springframework.context.annotation.Lazy; -import org.springframework.stereotype.Service; -import org.springframework.validation.annotation.Validated; - -import jakarta.annotation.Resource; -import jakarta.validation.Validator; -import java.util.List; - -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.*; - -/** - * 公众号的自动回复 Service 实现类 - * - * @author 芋道源码 - */ -@Service -@Validated -public class MpAutoReplyServiceImpl implements MpAutoReplyService { - - @Resource - private MpMessageService mpMessageService; - @Resource - @Lazy // 延迟加载,避免循环依赖 - private MpAccountService mpAccountService; - - @Resource - private Validator validator; - - @Resource - private MpAutoReplyMapper mpAutoReplyMapper; - - @Override - public PageResult getAutoReplyPage(MpMessagePageReqVO pageVO) { - return mpAutoReplyMapper.selectPage(pageVO); - } - - @Override - public MpAutoReplyDO getAutoReply(Long id) { - return mpAutoReplyMapper.selectById(id); - } - - @Override - public Long createAutoReply(MpAutoReplyCreateReqVO createReqVO) { - // 第一步,校验数据 - if (createReqVO.getResponseMessageType() != null) { - MpUtils.validateMessage(validator, createReqVO.getResponseMessageType(), createReqVO); - } - validateAutoReplyConflict(null, createReqVO.getAccountId(), createReqVO.getType(), - createReqVO.getRequestKeyword(), createReqVO.getRequestMessageType()); - - // 第二步,插入数据 - MpAccountDO account = mpAccountService.getRequiredAccount(createReqVO.getAccountId()); - MpAutoReplyDO autoReply = MpAutoReplyConvert.INSTANCE.convert(createReqVO) - .setAppId(account.getAppId()); - mpAutoReplyMapper.insert(autoReply); - return autoReply.getId(); - } - - @Override - public void updateAutoReply(MpAutoReplyUpdateReqVO updateReqVO) { - // 第一步,校验数据 - if (updateReqVO.getResponseMessageType() != null) { - MpUtils.validateMessage(validator, updateReqVO.getResponseMessageType(), updateReqVO); - } - MpAutoReplyDO autoReply = validateAutoReplyExists(updateReqVO.getId()); - validateAutoReplyConflict(updateReqVO.getId(), autoReply.getAccountId(), updateReqVO.getType(), - updateReqVO.getRequestKeyword(), updateReqVO.getRequestMessageType()); - - // 第二步,更新数据 - MpAutoReplyDO updateObj = MpAutoReplyConvert.INSTANCE.convert(updateReqVO) - .setAccountId(null).setAppId(null); // 避免前端传递,更新着两个字段 - mpAutoReplyMapper.updateById(updateObj); - } - - /** - * 校验自动回复是否冲突 - * - * 不同的 type,会有不同的逻辑: - * 1. type = SUBSCRIBE 时,不允许有其他的自动回复 - * 2. type = MESSAGE 时,校验 requestMessageType 已经存在自动回复 - * 3. type = KEYWORD 时,校验 keyword 已经存在自动回复 - * - * @param id 自动回复编号 - * @param accountId 公众号账号的编号 - * @param type 类型 - * @param requestKeyword 请求关键词 - * @param requestMessageType 请求消息类型 - */ - private void validateAutoReplyConflict(Long id, Long accountId, Integer type, - String requestKeyword, String requestMessageType) { - // 获得已经存在的自动回复 - MpAutoReplyDO autoReply = null; - ErrorCode errorCode = null; - if (MpAutoReplyTypeEnum.SUBSCRIBE.getType().equals(type)) { - autoReply = mpAutoReplyMapper.selectByAccountIdAndSubscribe(accountId); - errorCode = AUTO_REPLY_ADD_SUBSCRIBE_FAIL_EXISTS; - } else if (MpAutoReplyTypeEnum.MESSAGE.getType().equals(type)) { - autoReply = mpAutoReplyMapper.selectByAccountIdAndMessage(accountId, requestMessageType); - errorCode = AUTO_REPLY_ADD_MESSAGE_FAIL_EXISTS; - } else if (MpAutoReplyTypeEnum.KEYWORD.getType().equals(type)) { - autoReply = mpAutoReplyMapper.selectByAccountIdAndKeyword(accountId, requestKeyword); - errorCode = AUTO_REPLY_ADD_KEYWORD_FAIL_EXISTS; - } - if (autoReply == null) { - return; - } - - // 存在冲突,抛出业务异常 - if (id == null // 情况一,新增(id == null),存在记录,说明冲突 - || ObjUtil.notEqual(id, autoReply.getId())) { // 情况二,修改(id != null),id 不匹配,说明冲突 - throw exception(errorCode); - } - } - - @Override - public void deleteAutoReply(Long id) { - // 校验粉丝存在 - validateAutoReplyExists(id); - - // 删除自动回复 - mpAutoReplyMapper.deleteById(id); - } - - private MpAutoReplyDO validateAutoReplyExists(Long id) { - MpAutoReplyDO autoReply = mpAutoReplyMapper.selectById(id); - if (autoReply == null) { - throw exception(AUTO_REPLY_NOT_EXISTS); - } - return autoReply; - } - - @Override - public WxMpXmlOutMessage replyForMessage(String appId, WxMpXmlMessage wxMessage) { - // 第一步,匹配自动回复 - List replies = null; - // 1.1 关键字 - if (wxMessage.getMsgType().equals(WxConsts.XmlMsgType.TEXT)) { - // 完全匹配 - replies = mpAutoReplyMapper.selectListByAppIdAndKeywordAll(appId, wxMessage.getContent()); - if (CollUtil.isEmpty(replies)) { - // 模糊匹配 - replies = mpAutoReplyMapper.selectListByAppIdAndKeywordLike(appId, wxMessage.getContent()); - } - } - // 1.2 消息类型 - if (CollUtil.isEmpty(replies)) { - replies = mpAutoReplyMapper.selectListByAppIdAndMessage(appId, wxMessage.getMsgType()); - } - if (CollUtil.isEmpty(replies)) { - return null; - } - MpAutoReplyDO reply = CollUtil.getFirst(replies); - - // 第二步,基于自动回复,创建消息 - MpMessageSendOutReqBO sendReqBO = MpAutoReplyConvert.INSTANCE.convert(wxMessage.getFromUser(), reply); - return mpMessageService.sendOutMessage(sendReqBO); - } - - @Override - public WxMpXmlOutMessage replyForSubscribe(String appId, WxMpXmlMessage wxMessage) { - // 第一步,匹配自动回复 - List replies = mpAutoReplyMapper.selectListByAppIdAndSubscribe(appId); - MpAutoReplyDO reply = CollUtil.isNotEmpty(replies) ? CollUtil.getFirst(replies) - : buildDefaultSubscribeAutoReply(appId); // 如果不存在,提供一个默认末班 - - // 第二步,基于自动回复,创建消息 - MpMessageSendOutReqBO sendReqBO = MpAutoReplyConvert.INSTANCE.convert(wxMessage.getFromUser(), reply); - return mpMessageService.sendOutMessage(sendReqBO); - } - - private MpAutoReplyDO buildDefaultSubscribeAutoReply(String appId) { - MpAccountDO account = mpAccountService.getAccountFromCache(appId); - Assert.notNull(account, "公众号账号({}) 不存在", appId); - // 构建默认的【关注】自动回复 - return new MpAutoReplyDO().setAppId(appId).setAccountId(account.getId()) - .setType(MpAutoReplyTypeEnum.SUBSCRIBE.getType()) - .setResponseMessageType(WxConsts.XmlMsgType.TEXT).setResponseContent("感谢关注"); - } - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageService.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageService.java deleted file mode 100644 index 4f77cbe..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageService.java +++ /dev/null @@ -1,59 +0,0 @@ -package cn.iocoder.yudao.module.mp.service.message; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.mp.controller.admin.message.vo.message.MpMessagePageReqVO; -import cn.iocoder.yudao.module.mp.controller.admin.message.vo.message.MpMessageSendReqVO; -import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO; -import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO; -import me.chanjar.weixin.mp.api.WxMpService; -import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; -import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; - -import jakarta.validation.Valid; - -/** - * 公众号消息 Service 接口 - * - * @author 芋道源码 - */ -public interface MpMessageService { - - /** - * 获得公众号消息分页 - * - * @param pageReqVO 分页查询 - * @return 公众号消息分页 - */ - PageResult getMessagePage(MpMessagePageReqVO pageReqVO); - - /** - * 从公众号,接收到粉丝消息 - * - * @param appId 微信公众号 appId - * @param wxMessage 消息 - */ - void receiveMessage(WxMpService weixinService, String appId, WxMpXmlMessage wxMessage); - - /** - * 使用公众号,给粉丝回复消息 - * - * 例如说:自动回复、客服消息、菜单回复消息等场景 - * - * 注意,该方法只是返回 WxMpXmlOutMessage 对象,不会真的发送消息 - * - * @param sendReqBO 消息内容 - * @return 微信回复消息 XML - */ - WxMpXmlOutMessage sendOutMessage(@Valid MpMessageSendOutReqBO sendReqBO); - - /** - * 使用公众号,给粉丝发送【客服】消息 - * - * 注意,该方法会真实发送消息 - * - * @param sendReqVO 消息内容 - * @return 消息 - */ - MpMessageDO sendKefuMessage(MpMessageSendReqVO sendReqVO); - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageServiceImpl.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageServiceImpl.java deleted file mode 100644 index 4a511c8..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageServiceImpl.java +++ /dev/null @@ -1,172 +0,0 @@ -package cn.iocoder.yudao.module.mp.service.message; - -import cn.hutool.core.lang.Assert; -import cn.hutool.core.thread.ThreadUtil; -import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.mp.controller.admin.message.vo.message.MpMessagePageReqVO; -import cn.iocoder.yudao.module.mp.controller.admin.message.vo.message.MpMessageSendReqVO; -import cn.iocoder.yudao.module.mp.convert.message.MpMessageConvert; -import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; -import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO; -import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO; -import cn.iocoder.yudao.module.mp.dal.mysql.message.MpMessageMapper; -import cn.iocoder.yudao.module.mp.enums.message.MpMessageSendFromEnum; -import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory; -import cn.iocoder.yudao.module.mp.framework.mp.core.util.MpUtils; -import cn.iocoder.yudao.module.mp.service.account.MpAccountService; -import cn.iocoder.yudao.module.mp.service.material.MpMaterialService; -import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO; -import cn.iocoder.yudao.module.mp.service.user.MpUserService; -import jakarta.annotation.Resource; -import jakarta.validation.Validator; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; -import me.chanjar.weixin.common.api.WxConsts; -import me.chanjar.weixin.common.error.WxErrorException; -import me.chanjar.weixin.mp.api.WxMpService; -import me.chanjar.weixin.mp.bean.kefu.WxMpKefuMessage; -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.context.annotation.Lazy; -import org.springframework.stereotype.Service; -import org.springframework.validation.annotation.Validated; - -import java.util.concurrent.TimeUnit; - -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.MESSAGE_SEND_FAIL; - -/** - * 粉丝消息 Service 实现类 - * - * @author 芋道源码 - */ -@Service -@Validated -@Slf4j -public class MpMessageServiceImpl implements MpMessageService { - - @Resource - @Lazy // 延迟加载,避免循环依赖 - private MpAccountService mpAccountService; - @Resource - private MpUserService mpUserService; - @Resource - private MpMaterialService mpMaterialService; - - @Resource - private MpMessageMapper mpMessageMapper; - - @Resource - @Lazy // 延迟加载,解决循环依赖的问题 - private MpServiceFactory mpServiceFactory; - - @Resource - private Validator validator; - - @Override - public PageResult getMessagePage(MpMessagePageReqVO pageReqVO) { - return mpMessageMapper.selectPage(pageReqVO); - } - - @Override - @SneakyThrows - public void receiveMessage(WxMpService weixinService, String appId, WxMpXmlMessage wxMessage) { - // 获得关联信息 - MpAccountDO account = mpAccountService.getAccountFromCache(appId); - Assert.notNull(account, "公众号账号({}) 不存在", appId); - - // 获取用户 - MpUserDO user = mpUserService.getUser(appId, wxMessage.getFromUser()); - if (user == null) { - // 特殊情况:因为 receiveMessage 是异步记录,可能 SubscribeHandler 还没存储好 User,此时 sleep 轮询 - for (int i = 0; i < 3; i++) { - ThreadUtil.sleep(5, TimeUnit.SECONDS); - user = mpUserService.getUser(appId, wxMessage.getFromUser()); - if (user != null) { - break; - } - log.warn("[receiveMessage][粉丝({}/{}) 不存在,第 {} 次重试失败]", appId, wxMessage.getFromUser(), i + 1); - } - } - // 特殊情况:可能 SubscribeHandler 没处理正确(例如说发生异常),则主动创建 - if (user == null) { - log.warn("[receiveMessage][粉丝({}/{}) 不存在,主动创建]", appId, wxMessage.getFromUser()); - WxMpUser wxMpUser = weixinService.getUserService().userInfo(wxMessage.getFromUser()); - user = mpUserService.saveUser(appId, wxMpUser); - } - Assert.notNull(user, "公众号粉丝({}/{}) 不存在", appId, wxMessage.getFromUser()); - - // 记录消息 - MpMessageDO message = MpMessageConvert.INSTANCE.convert(wxMessage, account, user) - .setSendFrom(MpMessageSendFromEnum.USER_TO_MP.getFrom()); - downloadMessageMedia(message); - mpMessageMapper.insert(message); - } - - @Override - public WxMpXmlOutMessage sendOutMessage(MpMessageSendOutReqBO sendReqBO) { - // 校验消息格式 - MpUtils.validateMessage(validator, sendReqBO.getType(), sendReqBO); - - // 获得关联信息 - MpAccountDO account = mpAccountService.getAccountFromCache(sendReqBO.getAppId()); - Assert.notNull(account, "公众号账号({}) 不存在", sendReqBO.getAppId()); - MpUserDO user = mpUserService.getUser(sendReqBO.getAppId(), sendReqBO.getOpenid()); - Assert.notNull(user, "公众号粉丝({}/{}) 不存在", sendReqBO.getAppId(), sendReqBO.getOpenid()); - - // 记录消息 - MpMessageDO message = MpMessageConvert.INSTANCE.convert(sendReqBO, account, user). - setSendFrom(MpMessageSendFromEnum.MP_TO_USER.getFrom()); - downloadMessageMedia(message); - mpMessageMapper.insert(message); - - // 转换返回 WxMpXmlOutMessage 对象 - return MpMessageConvert.INSTANCE.convert02(message, account); - } - - @Override - public MpMessageDO sendKefuMessage(MpMessageSendReqVO sendReqVO) { - // 校验消息格式 - MpUtils.validateMessage(validator, sendReqVO.getType(), sendReqVO); - - // 获得关联信息 - MpUserDO user = mpUserService.getRequiredUser(sendReqVO.getUserId()); - MpAccountDO account = mpAccountService.getRequiredAccount(user.getAccountId()); - - // 发送客服消息 - WxMpKefuMessage wxMessage = MpMessageConvert.INSTANCE.convert(sendReqVO, user); - WxMpService mpService = mpServiceFactory.getRequiredMpService(user.getAppId()); - try { - mpService.getKefuService().sendKefuMessageWithResponse(wxMessage); - } catch (WxErrorException e) { - throw exception(MESSAGE_SEND_FAIL, e.getError().getErrorMsg()); - } - - // 记录消息 - MpMessageDO message = MpMessageConvert.INSTANCE.convert(wxMessage, account, user) - .setSendFrom(MpMessageSendFromEnum.MP_TO_USER.getFrom()); - downloadMessageMedia(message); - mpMessageMapper.insert(message); - return message; - } - - /** - * 下载消息使用到的媒体文件,并上传到文件服务 - * - * @param message 消息 - */ - private void downloadMessageMedia(MpMessageDO message) { - if (StrUtil.isNotEmpty(message.getMediaId())) { - message.setMediaUrl(mpMaterialService.downloadMaterialUrl(message.getAccountId(), - message.getMediaId(), MpUtils.getMediaFileType(message.getType()))); - } - if (StrUtil.isNotEmpty(message.getThumbMediaId())) { - message.setThumbMediaUrl(mpMaterialService.downloadMaterialUrl(message.getAccountId(), - message.getThumbMediaId(), WxConsts.MediaFileType.THUMB)); - } - } - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/bo/MpMessageSendOutReqBO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/bo/MpMessageSendOutReqBO.java deleted file mode 100644 index 2a53649..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/bo/MpMessageSendOutReqBO.java +++ /dev/null @@ -1,110 +0,0 @@ -package cn.iocoder.yudao.module.mp.service.message.bo; - -import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO; -import cn.iocoder.yudao.module.mp.framework.mp.core.util.MpUtils.*; -import lombok.Data; -import me.chanjar.weixin.common.api.WxConsts; -import org.hibernate.validator.constraints.URL; - -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; -import java.util.List; - -/** - * 公众号消息发送 Request BO - * - * 为什么要有该 BO 呢?在自动回复、客服消息、菜单回复消息等场景,都涉及到 MP 给粉丝发送消息,所以使用该 BO 统一承接 - * - * @author 芋道源码 - */ -@Data -public class MpMessageSendOutReqBO { - - /** - * 公众号 appId - */ - @NotEmpty(message = "公众号 appId 不能为空") - private String appId; - /** - * 公众号粉丝 openid - */ - @NotEmpty(message = "公众号粉丝 openid 不能为空") - private String openid; - - // ========== 消息内容 ========== - /** - * 消息类型 - * - * 枚举 {@link WxConsts.XmlMsgType} 中的 TEXT、IMAGE、VOICE、VIDEO、NEWS、MUSIC - */ - @NotEmpty(message = "消息类型不能为空") - public String type; - - /** - * 消息内容 - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 TEXT - */ - @NotEmpty(message = "消息内容不能为空", groups = TextMessageGroup.class) - private String content; - - /** - * 媒体 id - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 IMAGE、VOICE、VIDEO - */ - @NotEmpty(message = "消息 mediaId 不能为空", groups = {ImageMessageGroup.class, VoiceMessageGroup.class, VideoMessageGroup.class}) - private String mediaId; - - /** - * 缩略图的媒体 id - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO、MUSIC - */ - @NotEmpty(message = "消息 thumbMediaId 不能为空", groups = {MusicMessageGroup.class}) - private String thumbMediaId; - - /** - * 标题 - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO - */ - @NotEmpty(message = "消息标题不能为空", groups = VideoMessageGroup.class) - private String title; - /** - * 描述 - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO - */ - @NotEmpty(message = "消息描述不能为空", groups = VideoMessageGroup.class) - private String description; - - /** - * 图文消息 - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS - */ - @Valid - @NotNull(message = "图文消息不能为空", groups = NewsMessageGroup.class) - private List articles; - - /** - * 音乐链接 - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC - */ - @NotEmpty(message = "音乐链接不能为空", groups = MusicMessageGroup.class) - @URL(message = "高质量音乐链接格式不正确", groups = MusicMessageGroup.class) - private String musicUrl; - - /** - * 高质量音乐链接 - * - * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC - */ - @NotEmpty(message = "高质量音乐链接不能为空", groups = MusicMessageGroup.class) - @URL(message = "高质量音乐链接格式不正确", groups = MusicMessageGroup.class) - private String hqMusicUrl; - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/statistics/MpStatisticsService.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/statistics/MpStatisticsService.java deleted file mode 100644 index 08e5c35..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/statistics/MpStatisticsService.java +++ /dev/null @@ -1,54 +0,0 @@ -package cn.iocoder.yudao.module.mp.service.statistics; - -import me.chanjar.weixin.mp.bean.datacube.WxDataCubeInterfaceResult; -import me.chanjar.weixin.mp.bean.datacube.WxDataCubeMsgResult; -import me.chanjar.weixin.mp.bean.datacube.WxDataCubeUserCumulate; -import me.chanjar.weixin.mp.bean.datacube.WxDataCubeUserSummary; - -import java.time.LocalDateTime; -import java.util.List; - -/** - * 公众号统计 Service 接口 - * - * @author 芋道源码 - */ -public interface MpStatisticsService { - - /** - * 获取粉丝增减数据 - * - * @param accountId 公众号账号编号 - * @param date 时间区间 - * @return 粉丝增减数据 - */ - List getUserSummary(Long accountId, LocalDateTime[] date); - - /** - * 获取粉丝累计数据 - * - * @param accountId 公众号账号编号 - * @param date 时间区间 - * @return 粉丝累计数据 - */ - List getUserCumulate(Long accountId, LocalDateTime[] date); - - /** - * 获取消息发送概况数据 - * - * @param accountId 公众号账号编号 - * @param date 时间区间 - * @return 消息发送概况数据 - */ - List getUpstreamMessage(Long accountId, LocalDateTime[] date); - - /** - * 获取接口分析数据 - * - * @param accountId 公众号账号编号 - * @param date 时间区间 - * @return 接口分析数据 - */ - List getInterfaceSummary(Long accountId, LocalDateTime[] date); - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/statistics/MpStatisticsServiceImpl.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/statistics/MpStatisticsServiceImpl.java deleted file mode 100644 index 5ee2073..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/statistics/MpStatisticsServiceImpl.java +++ /dev/null @@ -1,77 +0,0 @@ -package cn.iocoder.yudao.module.mp.service.statistics; - -import cn.hutool.core.date.DateUtil; -import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory; -import me.chanjar.weixin.common.error.WxErrorException; -import me.chanjar.weixin.mp.api.WxMpService; -import me.chanjar.weixin.mp.bean.datacube.WxDataCubeInterfaceResult; -import me.chanjar.weixin.mp.bean.datacube.WxDataCubeMsgResult; -import me.chanjar.weixin.mp.bean.datacube.WxDataCubeUserCumulate; -import me.chanjar.weixin.mp.bean.datacube.WxDataCubeUserSummary; -import org.springframework.context.annotation.Lazy; -import org.springframework.stereotype.Service; - -import jakarta.annotation.Resource; -import java.time.LocalDateTime; -import java.util.List; - -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.*; - -/** - * 公众号统计 Service 实现类 - * - * @author 芋道源码 - */ -@Service -public class MpStatisticsServiceImpl implements MpStatisticsService { - - @Resource - @Lazy // 延迟加载,解决循环依赖的问题 - private MpServiceFactory mpServiceFactory; - - @Override - public List getUserSummary(Long accountId, LocalDateTime[] date) { - WxMpService mpService = mpServiceFactory.getRequiredMpService(accountId); - try { - return mpService.getDataCubeService().getUserSummary( - DateUtil.date(date[0]), DateUtil.date(date[1])); - } catch (WxErrorException e) { - throw exception(STATISTICS_GET_USER_SUMMARY_FAIL, e.getError().getErrorMsg()); - } - } - - @Override - public List getUserCumulate(Long accountId, LocalDateTime[] date) { - WxMpService mpService = mpServiceFactory.getRequiredMpService(accountId); - try { - return mpService.getDataCubeService().getUserCumulate( - DateUtil.date(date[0]), DateUtil.date(date[1])); - } catch (WxErrorException e) { - throw exception(STATISTICS_GET_USER_CUMULATE_FAIL, e.getError().getErrorMsg()); - } - } - - @Override - public List getUpstreamMessage(Long accountId, LocalDateTime[] date) { - WxMpService mpService = mpServiceFactory.getRequiredMpService(accountId); - try { - return mpService.getDataCubeService().getUpstreamMsg( - DateUtil.date(date[0]), DateUtil.date(date[1])); - } catch (WxErrorException e) { - throw exception(STATISTICS_GET_UPSTREAM_MESSAGE_FAIL, e.getError().getErrorMsg()); - } - } - - @Override - public List getInterfaceSummary(Long accountId, LocalDateTime[] date) { - WxMpService mpService = mpServiceFactory.getRequiredMpService(accountId); - try { - return mpService.getDataCubeService().getInterfaceSummary( - DateUtil.date(date[0]), DateUtil.date(date[1])); - } catch (WxErrorException e) { - throw exception(STATISTICS_GET_INTERFACE_SUMMARY_FAIL, e.getError().getErrorMsg()); - } - } - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/tag/MpTagService.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/tag/MpTagService.java deleted file mode 100644 index eb32486..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/tag/MpTagService.java +++ /dev/null @@ -1,65 +0,0 @@ -package cn.iocoder.yudao.module.mp.service.tag; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.mp.controller.admin.tag.vo.MpTagCreateReqVO; -import cn.iocoder.yudao.module.mp.controller.admin.tag.vo.MpTagPageReqVO; -import cn.iocoder.yudao.module.mp.controller.admin.tag.vo.MpTagUpdateReqVO; -import cn.iocoder.yudao.module.mp.dal.dataobject.tag.MpTagDO; - -import jakarta.validation.Valid; -import java.util.List; - -/** - * 公众号标签 Service 接口 - * - * @author fengdan - */ -public interface MpTagService { - - /** - * 创建公众号标签 - * - * @param createReqVO 创建标签信息 - * @return 标签编号 - */ - Long createTag(@Valid MpTagCreateReqVO createReqVO); - - /** - * 更新公众号标签 - * - * @param updateReqVO 更新标签信息 - */ - void updateTag(@Valid MpTagUpdateReqVO updateReqVO); - - /** - * 删除公众号标签 - * - * @param id 编号 - */ - void deleteTag(Long id); - - /** - * 获得公众号标签分页 - * - * @param pageReqVO 分页查询 - * @return 公众号标签分页 - */ - PageResult getTagPage(MpTagPageReqVO pageReqVO); - - /** - * 获得公众号标签详情 - * @param id id查询 - * @return 公众号标签详情 - */ - MpTagDO get(Long id); - - List getTagList(); - - /** - * 同步公众号标签 - * - * @param accountId 公众号账号的编号 - */ - void syncTag(Long accountId); - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/tag/MpTagServiceImpl.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/tag/MpTagServiceImpl.java deleted file mode 100644 index f48c1ab..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/tag/MpTagServiceImpl.java +++ /dev/null @@ -1,164 +0,0 @@ -package cn.iocoder.yudao.module.mp.service.tag; - -import cn.hutool.core.collection.CollUtil; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.mp.controller.admin.tag.vo.MpTagCreateReqVO; -import cn.iocoder.yudao.module.mp.controller.admin.tag.vo.MpTagPageReqVO; -import cn.iocoder.yudao.module.mp.controller.admin.tag.vo.MpTagUpdateReqVO; -import cn.iocoder.yudao.module.mp.convert.tag.MpTagConvert; -import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; -import cn.iocoder.yudao.module.mp.dal.dataobject.tag.MpTagDO; -import cn.iocoder.yudao.module.mp.dal.mysql.tag.MpTagMapper; -import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory; -import cn.iocoder.yudao.module.mp.service.account.MpAccountService; -import lombok.extern.slf4j.Slf4j; -import me.chanjar.weixin.common.error.WxErrorException; -import me.chanjar.weixin.mp.api.WxMpService; -import me.chanjar.weixin.mp.bean.tag.WxUserTag; -import org.springframework.context.annotation.Lazy; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.validation.annotation.Validated; - -import jakarta.annotation.Resource; -import java.util.List; -import java.util.Map; - -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; -import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.*; - -/** - * 公众号标签 Service 实现类 - * - * @author fengdan - */ -@Slf4j -@Service -@Validated -public class MpTagServiceImpl implements MpTagService { - - @Resource - private MpTagMapper mpTagMapper; - - @Resource - private MpAccountService mpAccountService; - - @Resource - @Lazy // 延迟加载,为了解决延迟加载 - private MpServiceFactory mpServiceFactory; - - @Override - public Long createTag(MpTagCreateReqVO createReqVO) { - // 获得公众号账号 - MpAccountDO account = mpAccountService.getRequiredAccount(createReqVO.getAccountId()); - - // 第一步,新增标签到公众号平台。标签名的唯一,交给公众号平台 - WxMpService mpService = mpServiceFactory.getRequiredMpService(createReqVO.getAccountId()); - WxUserTag wxTag; - try { - wxTag = mpService.getUserTagService().tagCreate(createReqVO.getName()); - } catch (WxErrorException e) { - throw exception(TAG_CREATE_FAIL, e.getError().getErrorMsg()); - } - - // 第二步,新增标签到数据库 - MpTagDO tag = MpTagConvert.INSTANCE.convert(wxTag, account); - mpTagMapper.insert(tag); - return tag.getId(); - } - - @Override - public void updateTag(MpTagUpdateReqVO updateReqVO) { - // 校验标签存在 - MpTagDO tag = validateTagExists(updateReqVO.getId()); - - // 第一步,更新标签到公众号平台。标签名的唯一,交给公众号平台 - WxMpService mpService = mpServiceFactory.getRequiredMpService(tag.getAccountId()); - try { - mpService.getUserTagService().tagUpdate(tag.getTagId(), updateReqVO.getName()); - } catch (WxErrorException e) { - throw exception(TAG_UPDATE_FAIL, e.getError().getErrorMsg()); - } - - // 第二步,更新标签到数据库 - mpTagMapper.updateById(new MpTagDO().setId(tag.getId()).setName(updateReqVO.getName())); - } - - @Override - public void deleteTag(Long id) { - // 校验标签存在 - MpTagDO tag = validateTagExists(id); - - // 第一步,删除标签到公众号平台。 - WxMpService mpService = mpServiceFactory.getRequiredMpService(tag.getAccountId()); - try { - mpService.getUserTagService().tagDelete(tag.getTagId()); - } catch (WxErrorException e) { - throw exception(TAG_DELETE_FAIL, e.getError().getErrorMsg()); - } - - // 第二步,删除标签到数据库 - mpTagMapper.deleteById(tag.getId()); - } - - private MpTagDO validateTagExists(Long id) { - MpTagDO tag = mpTagMapper.selectById(id); - if (tag == null) { - throw exception(TAG_NOT_EXISTS); - } - return tag; - } - - @Override - public PageResult getTagPage(MpTagPageReqVO pageReqVO) { - return mpTagMapper.selectPage(pageReqVO); - } - - @Override - public MpTagDO get(Long id) { - return mpTagMapper.selectById(id); - } - - @Override - public List getTagList() { - return mpTagMapper.selectList(); - } - - @Override - @Transactional(rollbackFor = Exception.class) - public void syncTag(Long accountId) { - MpAccountDO account = mpAccountService.getRequiredAccount(accountId); - - // 第一步,从公众号平台获取最新的标签列表 - WxMpService mpService = mpServiceFactory.getRequiredMpService(accountId); - List wxTags; - try { - wxTags = mpService.getUserTagService().tagGet(); - } catch (WxErrorException e) { - throw exception(TAG_GET_FAIL, e.getError().getErrorMsg()); - } - - // 第二步,合并更新回自己的数据库;由于标签只有 100 个,所以直接 for 循环操作 - Map tagMap = convertMap(mpTagMapper.selectListByAccountId(accountId), - MpTagDO::getTagId); - wxTags.forEach(wxTag -> { - MpTagDO tag = tagMap.remove(wxTag.getId()); - // 情况一,不存在,新增 - if (tag == null) { - tag = MpTagConvert.INSTANCE.convert(wxTag, account); - mpTagMapper.insert(tag); - return; - } - // 情况二,存在,则更新 - mpTagMapper.updateById(new MpTagDO().setId(tag.getId()) - .setName(wxTag.getName()).setCount(wxTag.getCount())); - }); - // 情况三,部分标签已经不存在了,删除 - if (CollUtil.isNotEmpty(tagMap)) { - mpTagMapper.deleteBatchIds(convertList(tagMap.values(), MpTagDO::getId)); - } - } - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/user/MpUserService.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/user/MpUserService.java deleted file mode 100644 index bdefc0e..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/user/MpUserService.java +++ /dev/null @@ -1,102 +0,0 @@ -package cn.iocoder.yudao.module.mp.service.user; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.mp.controller.admin.user.vo.MpUserPageReqVO; -import cn.iocoder.yudao.module.mp.controller.admin.user.vo.MpUserUpdateReqVO; -import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO; -import me.chanjar.weixin.mp.bean.result.WxMpUser; - -import java.util.Collection; -import java.util.List; - -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.USER_NOT_EXISTS; - -/** - * 公众号粉丝 Service 接口 - * - * @author 芋道源码 - */ -public interface MpUserService { - - /** - * 获得公众号粉丝 - * - * @param id 编号 - * @return 公众号粉丝 - */ - MpUserDO getUser(Long id); - - /** - * 使用 appId + openId,获得公众号粉丝 - * - * @param appId 公众号 appId - * @param openId 公众号 openId - * @return 公众号粉丝 - */ - MpUserDO getUser(String appId, String openId); - - /** - * 获得公众号粉丝 - * - * @param id 编号 - * @return 公众号粉丝 - */ - default MpUserDO getRequiredUser(Long id) { - MpUserDO user = getUser(id); - if (user == null) { - throw exception(USER_NOT_EXISTS); - } - return user; - } - - /** - * 获得公众号粉丝列表 - * - * @param ids 编号 - * @return 公众号粉丝列表 - */ - List getUserList(Collection ids); - - /** - * 获得公众号粉丝分页 - * - * @param pageReqVO 分页查询 - * @return 公众号粉丝分页 - */ - PageResult getUserPage(MpUserPageReqVO pageReqVO); - - /** - * 保存公众号粉丝 - * - * 新增或更新,根据是否存在数据库中 - * - * @param appId 公众号 appId - * @param wxMpUser 公众号粉丝的信息 - * @return 公众号粉丝 - */ - MpUserDO saveUser(String appId, WxMpUser wxMpUser); - - /** - * 同步一个公众号粉丝 - * - * @param accountId 公众号账号的编号 - */ - void syncUser(Long accountId); - - /** - * 更新公众号粉丝,取消关注 - * - * @param appId 公众号 appId - * @param openId 公众号粉丝的 openid - */ - void updateUserUnsubscribe(String appId, String openId); - - /** - * 更新公众号粉丝 - * - * @param updateReqVO 更新信息 - */ - void updateUser(MpUserUpdateReqVO updateReqVO); - -} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/user/MpUserServiceImpl.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/user/MpUserServiceImpl.java deleted file mode 100644 index 12dd009..0000000 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/user/MpUserServiceImpl.java +++ /dev/null @@ -1,215 +0,0 @@ -package cn.iocoder.yudao.module.mp.service.user; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; -import cn.iocoder.yudao.module.mp.controller.admin.user.vo.MpUserPageReqVO; -import cn.iocoder.yudao.module.mp.controller.admin.user.vo.MpUserUpdateReqVO; -import cn.iocoder.yudao.module.mp.convert.user.MpUserConvert; -import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; -import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO; -import cn.iocoder.yudao.module.mp.dal.mysql.user.MpUserMapper; -import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory; -import cn.iocoder.yudao.module.mp.service.account.MpAccountService; -import lombok.extern.slf4j.Slf4j; -import me.chanjar.weixin.common.error.WxErrorException; -import me.chanjar.weixin.mp.api.WxMpService; -import me.chanjar.weixin.mp.bean.result.WxMpUser; -import me.chanjar.weixin.mp.bean.result.WxMpUserList; -import org.springframework.context.annotation.Lazy; -import org.springframework.scheduling.annotation.Async; -import org.springframework.stereotype.Service; -import org.springframework.validation.annotation.Validated; - -import jakarta.annotation.Resource; -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; - -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.USER_NOT_EXISTS; -import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.USER_UPDATE_TAG_FAIL; - -/** - * 微信公众号粉丝 Service 实现类 - * - * @author 芋道源码 - */ -@Service -@Validated -@Slf4j -public class MpUserServiceImpl implements MpUserService { - - @Resource - @Lazy // 延迟加载,解决循环依赖的问题 - private MpAccountService mpAccountService; - - @Resource - @Lazy // 延迟加载,解决循环依赖的问题 - private MpServiceFactory mpServiceFactory; - - @Resource - private MpUserMapper mpUserMapper; - - @Override - public MpUserDO getUser(Long id) { - return mpUserMapper.selectById(id); - } - - @Override - public MpUserDO getUser(String appId, String openId) { - return mpUserMapper.selectByAppIdAndOpenid(appId, openId); - } - - @Override - public List getUserList(Collection ids) { - return mpUserMapper.selectBatchIds(ids); - } - - @Override - public PageResult getUserPage(MpUserPageReqVO pageReqVO) { - return mpUserMapper.selectPage(pageReqVO); - } - - @Override - public MpUserDO saveUser(String appId, WxMpUser wxMpUser) { - // 构建保存的 MpUserDO 对象 - MpAccountDO account = mpAccountService.getAccountFromCache(appId); - MpUserDO user = MpUserConvert.INSTANCE.convert(account, wxMpUser); - - // 根据情况,插入或更新 - MpUserDO dbUser = mpUserMapper.selectByAppIdAndOpenid(appId, wxMpUser.getOpenId()); - if (dbUser == null) { - mpUserMapper.insert(user); - } else { - user.setId(dbUser.getId()); - mpUserMapper.updateById(user); - } - return user; - } - - @Override - @Async - public void syncUser(Long accountId) { - MpAccountDO account = mpAccountService.getRequiredAccount(accountId); - // for 循环,避免递归出意外问题,导致死循环 - String nextOpenid = null; - for (int i = 0; i < Short.MAX_VALUE; i++) { - log.info("[syncUser][第({}) 次加载公众号粉丝列表,nextOpenid({})]", i, nextOpenid); - try { - nextOpenid = syncUser0(account, nextOpenid); - } catch (WxErrorException e) { - log.error("[syncUser][第({}) 次同步粉丝异常]", i, e); - break; - } - // 如果 nextOpenid 为空,表示已经同步完毕 - if (StrUtil.isEmpty(nextOpenid)) { - break; - } - } - } - - private String syncUser0(MpAccountDO account, String nextOpenid) throws WxErrorException { - // 第一步,从公众号流式加载粉丝 - WxMpService mpService = mpServiceFactory.getRequiredMpService(account.getId()); - WxMpUserList wxUserList = mpService.getUserService().userList(nextOpenid); - if (CollUtil.isEmpty(wxUserList.getOpenids())) { - return null; - } - - // 第二步,分批加载粉丝信息 - List> openidsList = CollUtil.split(wxUserList.getOpenids(), 100); - for (List openids : openidsList) { - log.info("[syncUser][批量加载粉丝信息,openids({})]", openids); - List wxUsers = mpService.getUserService().userInfoList(openids); - batchSaveUser(account, wxUsers); - } - - // 返回下一次的 nextOpenId - return wxUserList.getNextOpenid(); - } - - private void batchSaveUser(MpAccountDO account, List wxUsers) { - if (CollUtil.isEmpty(wxUsers)) { - return; - } - // 1. 获得数据库已保存的粉丝列表 - List dbUsers = mpUserMapper.selectListByAppIdAndOpenid(account.getAppId(), - CollectionUtils.convertList(wxUsers, WxMpUser::getOpenId)); - Map openId2Users = CollectionUtils.convertMap(dbUsers, MpUserDO::getOpenid); - - // 2.1 根据情况,插入或更新 - List users = MpUserConvert.INSTANCE.convertList(account, wxUsers); - List newUsers = new ArrayList<>(); - for (MpUserDO user : users) { - MpUserDO dbUser = openId2Users.get(user.getOpenid()); - if (dbUser == null) { // 新增:稍后批量插入 - newUsers.add(user); - } else { // 更新:直接执行更新 - user.setId(dbUser.getId()); - mpUserMapper.updateById(user); - } - } - // 2.2 批量插入 - if (CollUtil.isNotEmpty(newUsers)) { - mpUserMapper.insertBatch(newUsers); - } - } - - @Override - public void updateUserUnsubscribe(String appId, String openid) { - MpUserDO dbUser = mpUserMapper.selectByAppIdAndOpenid(appId, openid); - if (dbUser == null) { - log.error("[updateUserUnsubscribe][微信公众号粉丝 appId({}) openid({}) 不存在]", appId, openid); - return; - } - mpUserMapper.updateById(new MpUserDO().setId(dbUser.getId()).setSubscribeStatus(CommonStatusEnum.DISABLE.getStatus()) - .setUnsubscribeTime(LocalDateTime.now())); - } - - @Override - public void updateUser(MpUserUpdateReqVO updateReqVO) { - // 校验存在 - MpUserDO user = validateUserExists(updateReqVO.getId()); - - // 第一步,更新标签到公众号 - updateUserTag(user.getAppId(), user.getOpenid(), updateReqVO.getTagIds()); - - // 第二步,更新基本信息到数据库 - MpUserDO updateObj = MpUserConvert.INSTANCE.convert(updateReqVO).setId(user.getId()); - mpUserMapper.updateById(updateObj); - } - - private MpUserDO validateUserExists(Long id) { - MpUserDO user = mpUserMapper.selectById(id); - if (user == null) { - throw exception(USER_NOT_EXISTS); - } - return user; - } - - private void updateUserTag(String appId, String openid, List tagIds) { - WxMpService mpService = mpServiceFactory.getRequiredMpService(appId); - try { - // 第一步,先取消原来的标签 - List oldTagIds = mpService.getUserTagService().userTagList(openid); - for (Long tagId : oldTagIds) { - mpService.getUserTagService().batchUntagging(tagId, new String[]{openid}); - } - // 第二步,再设置新的标签 - if (CollUtil.isEmpty(tagIds)) { - return; - } - for (Long tagId: tagIds) { - mpService.getUserTagService().batchTagging(tagId, new String[]{openid}); - } - } catch (WxErrorException e) { - throw exception(USER_UPDATE_TAG_FAIL, e.getError().getErrorMsg()); - } - } - -} diff --git a/yudao-server/pom.xml b/yudao-server/pom.xml index fb9afb0..7ce0e36 100644 --- a/yudao-server/pom.xml +++ b/yudao-server/pom.xml @@ -61,7 +61,7 @@ - + -- libgit2 0.21.4