Explorar o código

mp:增加公众号的校验签名回调

YunaiV %!s(int64=2) %!d(string=hai) anos
pai
achega
c671b01b0a

+ 40 - 0
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 "非法请求";
+    }
+
+}

+ 34 - 0
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;
+
+}

+ 6 - 0
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;

+ 16 - 16
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;
     }
 
 }

+ 7 - 0
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 实例
      *

+ 2 - 0
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