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 new file mode 100644 index 000000000..a6284ab6d --- /dev/null +++ b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/open/MpOpenController.java @@ -0,0 +1,40 @@ +package cn.iocoder.yudao.module.mp.controller.admin.open; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.module.mp.controller.admin.open.vo.MpOpenCheckSignatureReqVO; +import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.mp.api.WxMpService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; + +@Api(tags = "管理后台 - 公众号回调") +@RestController +@RequestMapping("/mp/open") +@Validated +@Slf4j +public class MpOpenController { + + @Resource + private MpServiceFactory mpServiceFactory; + + @ApiOperation("校验签名") // 参见 https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html 文档 + @GetMapping(value = "/{appId}", produces = "text/plain;charset=utf-8") + 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 "非法请求"; + } + +} 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 new file mode 100644 index 000000000..23c3ab01d --- /dev/null +++ b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/open/vo/MpOpenCheckSignatureReqVO.java @@ -0,0 +1,34 @@ +package cn.iocoder.yudao.module.mp.controller.admin.open.vo; + + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.web.bind.annotation.RequestParam; + +import javax.validation.constraints.NotEmpty; + +@ApiModel("管理后台 - 公众号校验签名 Request VO") +@Data +public class MpOpenCheckSignatureReqVO { + + @ApiModelProperty(value = "微信加密签名", required = true, example = "490eb57f448b87bd5f20ccef58aa4de46aa1908e") + @NotEmpty(message = "微信加密签名不能为空") + private String signature; + + @ApiModelProperty(value = "时间戳", required = true, example = "1672587863") + @NotEmpty(message = "时间戳不能为空") + private String timestamp; + + @ApiModelProperty(value = "随机数", required = true, example = "1827365808") + @NotEmpty(message = "随机数不能为空") + private String nonce; + + @ApiModelProperty(value = "随机字符串", required = true, 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/package-info.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/package-info.java new file mode 100644 index 000000000..31a057767 --- /dev/null +++ b/yudao-module-mp/yudao-module-mp-biz/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/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 index 09ff871bd..cb93635af 100644 --- 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 @@ -94,65 +94,65 @@ public class DefaultMpServiceFactory implements MpServiceFactory { // 第二步,创建 WxMpService 对象 WxMpService service = new WxMpServiceImpl(); service.setWxMpConfigStorage(configStorage); - return null; + return service; } private WxMpMessageRouter buildMpMessageRouter(WxMpService mpService) { - final WxMpMessageRouter newRouter = new WxMpMessageRouter(mpService); + WxMpMessageRouter router = new WxMpMessageRouter(mpService); // 记录所有事件的日志(异步执行) - newRouter.rule().handler(logHandler).next(); + router.rule().handler(logHandler).next(); // 接收客服会话管理事件 - newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) + router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) .event(WxMpEventConstants.CustomerService.KF_CREATE_SESSION) .handler(kfSessionHandler).end(); - newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) + router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) .event(WxMpEventConstants.CustomerService.KF_CLOSE_SESSION) .handler(kfSessionHandler) .end(); - newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) + router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) .event(WxMpEventConstants.CustomerService.KF_SWITCH_SESSION) .handler(kfSessionHandler).end(); // 门店审核事件 - newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) + router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) .event(WxMpEventConstants.POI_CHECK_NOTIFY) .handler(storeCheckNotifyHandler).end(); // 自定义菜单事件 - newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) + router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) .event(WxConsts.MenuButtonType.CLICK).handler(menuHandler).end(); // 点击菜单连接事件 - newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) + router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) .event(WxConsts.MenuButtonType.VIEW).handler(nullHandler).end(); // 关注事件 - newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) + router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) .event(WxConsts.EventType.SUBSCRIBE).handler(subscribeHandler) .end(); // 取消关注事件 - newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) + router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) .event(WxConsts.EventType.UNSUBSCRIBE) .handler(unsubscribeHandler).end(); // 上报地理位置事件 - newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) + router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) .event(WxConsts.EventType.LOCATION).handler(locationHandler) .end(); // 接收地理位置消息 - newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.LOCATION) + router.rule().async(false).msgType(WxConsts.XmlMsgType.LOCATION) .handler(locationHandler).end(); // 扫码事件 - newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) + router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) .event(WxConsts.EventType.SCAN).handler(scanHandler).end(); // 默认 - newRouter.rule().async(false).handler(msgHandler).end(); - return newRouter; + router.rule().async(false).handler(msgHandler).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 index ba8dd87e3..12474be3c 100644 --- 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 @@ -1,5 +1,6 @@ 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; @@ -28,6 +29,12 @@ public interface MpServiceFactory { */ WxMpService getMpService(String appId); + default WxMpService getRequiredMpService(String appId) { + WxMpService wxMpService = getMpService(appId); + Assert.notNull(wxMpService, "找到对应 appId({}) 的 WxMpService,请核实!", appId); + return wxMpService; + } + /** * 获得 appId 对应的 WxMpMessageRouter 实例 * diff --git a/yudao-server/src/main/resources/application.yaml b/yudao-server/src/main/resources/application.yaml index d6b0ea3e8..247f23da9 100644 --- a/yudao-server/src/main/resources/application.yaml +++ b/yudao-server/src/main/resources/application.yaml @@ -92,6 +92,7 @@ yudao: security: permit-all_urls: - /admin-ui/** # /resources/admin-ui 目录下的静态资源 + - /admin-api/mp/open/** # 微信公众号开放平台,微信回调接口,不需要登录 swagger: title: 管理后台 description: 提供管理员管理的所有功能 @@ -119,6 +120,7 @@ yudao: - /admin-api/system/sms/callback/* # 短信回调接口,无法带上租户编号 - /admin-api/pay/notify/callback/* # 支付回调通知,不携带租户编号 - /jmreport/* # 积木报表,无法携带租户编号 + - /admin-api/mp/open/** # 微信公众号开放平台,微信回调接口,无法携带租户编号 ignore-tables: - system_tenant - system_tenant_package