Переглянути джерело

微信菜单接口 验签及消息推送接口

wwl 3 роки тому
батько
коміт
3d2bfed0df

+ 101 - 0
twzd-admin/src/main/java/com/miaxis/system/controller/wx/WxgzhController.java

@@ -0,0 +1,101 @@
+package com.miaxis.system.controller.wx;
+
+import com.alibaba.fastjson.JSONObject;
+import com.miaxis.common.constant.Constants;
+import com.miaxis.common.core.domain.Response;
+import com.miaxis.common.exception.CustomException;
+import com.miaxis.common.utils.wx.SignUtil;
+import com.miaxis.feign.service.IWxMpService;
+import com.miaxis.feign.service.IWxSendService;
+import com.miaxis.wx.service.IWxGzhService;
+import com.qcloud.cos.COSClient;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * 微信公众号相关
+ * @author wwl
+ * @version 1.0
+ * @date 2021/10/26 9:28
+ */
+@RestController
+@RequiredArgsConstructor
+@RequestMapping(Constants.OPEN_PREFIX + "/wx/gzh")
+@Api(tags={"微信公众号相关"})
+@Slf4j
+public class WxgzhController {
+
+    private final IWxSendService wxSendService;
+
+    private final IWxGzhService wxGzhService;
+
+    private final HttpServletRequest request;
+
+    @Value("${cos.bucketName}")
+    private String bucketName;
+    @Value("${cos.path}")
+    private String path;
+    @Value("${cos.preffix}")
+    private String preffix;
+
+
+    /**{"errcode":40017,"errmsg":"invalid button type rid: 61777f8d-226e0008-464b894c"}
+     * 获取微信公众号菜单
+     * @return
+     */
+    @GetMapping(value = "sendWxGzptMenu")
+    @ApiOperation("获取微信公众号菜单")
+    public Response sendWxGzptMenu(){
+        try{
+            String token = wxGzhService.getGzhToken();
+            JSONObject wxMenuJson = wxGzhService.getWxMenuJson();
+            log.info("--------"+wxMenuJson);
+            String result = wxSendService.createMenu(token,wxMenuJson);
+            return Response.success(result);
+        }catch (Exception e){
+            throw new CustomException("系统异常");
+        }
+    }
+
+
+    /**
+     * 处理服务器推送消息
+     * @param request
+     * @param response
+     * @return
+     */
+    @RequestMapping(value = "/notifyMsg",method = { RequestMethod.GET, RequestMethod.POST })
+    @ApiOperation("处理服务器推送消息")
+    public String checkSign(HttpServletRequest request, HttpServletResponse response) {
+        log.info("-----------进入---------");
+        try {
+            String signature = request.getParameter("signature");
+            String timestamp = request.getParameter("timestamp");
+            String nonce = request.getParameter("nonce");
+            String echostr = request.getParameter("echostr");
+            log.info("本身" + signature);
+            if(request.getMethod().equals("GET")){
+                log.info("-----------微信验签---------");
+                return SignUtil.checkSignature(signature, timestamp, nonce) == true ? echostr : null;
+            }else{
+                log.info("-----------推送消息处理---------");
+                return wxGzhService.handlePublicMsg(request);
+            }
+        } catch (Exception e) {
+            log.error("验证公众号token失败", e);
+        }
+        return null;
+    }
+
+
+}

+ 11 - 0
twzd-admin/src/main/resources/application-dev.yml

@@ -97,4 +97,15 @@ wxpay:
     notifyUrlRefund: http://218.85.55.253:65535/twzd-admin/open-api/wx/notify/refund
 
 
+# 腾讯cos
+cos:
+    secretId: AKIDwISNOFsJXYGjy89FJI9UnzuZFgTtRgFe
+    secretKey: IK5af8MJzPoKbdQxDCtKWR5T5PSEkyDB
+    bucket: ap-shanghai
+    bucketName: twzd-1305573081
+    path: https://twzd-1305573081.cos.ap-shanghai.myqcloud.com
+    preffix: twzd-test
+
+
+
 

+ 10 - 0
twzd-admin/src/main/resources/application-prod.yml

@@ -92,3 +92,13 @@ wxpay:
     v3key: 8604f2a6eb6338cfa64e7df4ec2c08b3
     notifyUrl: http://	jpcj-h5.zzxcx.net.zzxcx.net/prod-api/open-api/wx/notify/wxpay
     notifyUrlRefund: http://	jpcj-h5.zzxcx.net.zzxcx.net/prod-api/open-api/wx/notify/refund
+
+
+# 腾讯cos
+cos:
+    secretId: AKIDwISNOFsJXYGjy89FJI9UnzuZFgTtRgFe
+    secretKey: IK5af8MJzPoKbdQxDCtKWR5T5PSEkyDB
+    bucket: ap-shanghai
+    bucketName: twzd-1305573081
+    path: https://twzd-1305573081.cos.ap-shanghai.myqcloud.com
+    preffix: twzd-prod

+ 63 - 0
twzd-common/src/main/java/com/miaxis/common/config/CosConfig.java

@@ -0,0 +1,63 @@
+package com.miaxis.common.config;
+
+import com.qcloud.cos.COSClient;
+import com.qcloud.cos.ClientConfig;
+import com.qcloud.cos.auth.BasicCOSCredentials;
+import com.qcloud.cos.auth.COSCredentials;
+import com.qcloud.cos.http.HttpProtocol;
+import com.qcloud.cos.region.Region;
+import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * cos对象存储
+ * @author wwl
+ * @version 1.0
+ * @date 2021/10/25 15:40
+ */
+@Configuration
+public class CosConfig {
+
+    @Value("${cos.secretId}")
+    private String secretId;
+    @Value("${cos.secretKey}")
+    private String secretKey;
+    @Value("${cos.bucket}")
+    private String bucket;
+    @Value("${cos.bucketName}")
+    private String bucketName;
+    @Value("${cos.path}")
+    private String path;
+    @Value("${cos.preffix}")
+    private String preffix;
+
+
+    /**
+     * 腾讯云 COS
+     * @return
+     */
+
+    @Bean
+    public COSClient COSClientconfigBean() {
+        // 1 初始化用户身份信息(secretId, secretKey)。
+        // SECRETID和SECRETKEY请登录访问管理控制台 https://console.cloud.tencent.com/cam/capi 进行查看和管理
+        String secretId = this.secretId;
+        String secretKey = this.secretKey;
+        COSCredentials cred = new BasicCOSCredentials(secretId, secretKey);
+        // 2 设置 bucket 的地域, COS 地域的简称请参照 https://cloud.tencent.com/document/product/436/6224
+        // clientConfig 中包含了设置 region, https(默认 http), 超时, 代理等 set 方法, 使用可参见源码或者常见问题 Java SDK 部分。
+        Region region = new Region(this.bucket);
+        ClientConfig clientConfig = new ClientConfig(region);
+        // 这里建议设置使用 https 协议
+        // 从 5.6.54 版本开始,默认使用了 https
+        clientConfig.setHttpProtocol(HttpProtocol.https);
+        // 3 生成 cos 客户端。
+        COSClient cosClient = new COSClient(cred, clientConfig);
+        return cosClient;
+    }
+
+
+}

+ 79 - 0
twzd-common/src/main/java/com/miaxis/common/utils/wx/MessageUtil.java

@@ -0,0 +1,79 @@
+package com.miaxis.common.utils.wx;
+
+import cn.hutool.json.XML;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 微信消息工具
+ * @author wwl
+ * @version 1.0
+ * @date 2021/10/25 13:19
+ */
+public class MessageUtil {
+
+    public static final String MESSAGE_TEXT="text";//文本消息
+    public static final String MESSAGE_IMAGE="image";//图片消息
+    public static final String MESSAGE_VOICE="voice";//语音消息
+    public static final String MESSAGE_VIDEO="video";//视频消息
+    public static final String MESSAGE_LOCATION="LOCATION";//地理位置消息
+    public static final String MESSAGE_EVENT="event";//事件消息
+    public static final String MESSAGE_SUBSCRIBE="subscribe";//关注
+    public static final String MESSAGE_UNSUBSCRIBE="unsubscribe";//取消关注
+    public static final String MESSAGE_SCAN="SCAN";//已关注扫码消息
+    public static final String MESSAGE_CLICK="CLICK";//点击菜单拉取消息
+    public static final String MESSAGE_VIEW="VIEW";//点击菜单跳转链接消息
+    public static final String MESSAGE_VIEW_MINIPROGRAM="view_miniprogram";//点击菜单跳转小程序的消息
+
+
+
+    /**
+     * 回复文本消息
+     * @param toUserName
+     * @param fromUserName
+     * @param content
+     * @return
+     */
+    public static String initText(String toUserName, String fromUserName, String content) {
+        // 返回消息时ToUserName的值与FromUserName的互换
+        Map<String, String> returnMap = new HashMap<>();
+        returnMap.put("ToUserName", fromUserName);
+        returnMap.put("FromUserName", toUserName);
+        returnMap.put("CreateTime", new Date().getTime()+"");
+        returnMap.put("MsgType", "text");
+        returnMap.put("Content", content);
+        return  XML.toXml(returnMap);
+    }
+
+
+    /**
+     * 回复图文消息
+     * @param toUserName 接收方帐号(收到的OpenID)
+     * @param fromUserName 开发者微信号
+     * @param articles 图文消息信息,注意,如果图文数超过限制,则将只发限制内的条数
+     * @param title 图文消息标题
+     * @param description 图文消息描述
+     * @param picUrl 图片链接,支持JPG、PNG格式,较好的效果为大图360*200,小图200*200
+     * @param url 点击图文消息跳转链接
+     * @return
+     */
+    public static String initNews(String toUserName, String fromUserName, String articles, String title, String description, String picUrl, String url) {
+        // 返回消息时ToUserName的值与FromUserName的互换
+        Map<String, String> returnMap = new HashMap<>();
+        returnMap.put("ToUserName", fromUserName);
+        returnMap.put("FromUserName", toUserName);
+        returnMap.put("CreateTime", new Date().getTime()+"");
+        returnMap.put("MsgType", "news");
+        returnMap.put("ArticleCount", "1");
+        returnMap.put("Content", articles);
+        returnMap.put("Title", title);
+        returnMap.put("Description", description);
+        returnMap.put("PicUrl", picUrl);
+        returnMap.put("Url", url);
+        return  XML.toXml(returnMap);
+    }
+
+
+}

+ 14 - 0
twzd-service/src/main/java/com/miaxis/feign/service/IWxSendService.java

@@ -1,11 +1,13 @@
 package com.miaxis.feign.service;
 
+import com.alibaba.fastjson.JSONObject;
 import com.miaxis.common.config.FeignConfig;
 import com.miaxis.feign.dto.WxTicket;
 import org.springframework.cloud.openfeign.FeignClient;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
 
 /**
  * 微信公众号接口
@@ -42,4 +44,16 @@ public interface IWxSendService {
     String generateTicket(@RequestParam("access_token")String accessToken, WxTicket wxTicket);
 
 
+    /**
+     * 创建菜单接口
+     *      -文档链接:https://developers.weixin.qq.com/doc/offiaccount/Custom_Menus/Creating_Custom-Defined_Menu.html
+     * @param accessToken
+     * @param jsonObject json
+     * @return
+     */
+    @PostMapping(value = "/menu/create")
+    @ResponseBody
+    String createMenu(@RequestParam("access_token")String accessToken, JSONObject jsonObject);
+
+
 }

+ 69 - 0
twzd-service/src/main/java/com/miaxis/wx/domain/WxMenu.java

@@ -0,0 +1,69 @@
+package com.miaxis.wx.domain;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.miaxis.common.core.domain.BaseBusinessEntity;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 微信公众号菜单对象 wx_menu
+ * @author wwl
+ * @version 1.0
+ * @date 2021/10/26 10:06
+ */
+@Data
+@TableName("wx_menu")
+@ApiModel(value = "WxMenu", description = "微信公众号菜单对象 wx_menu")
+public class WxMenu extends BaseBusinessEntity {
+
+    private static final long serialVersionUID = 1L;
+
+    /** 主键 */
+    @TableId(value = "id")
+    @ApiModelProperty(value = "主键")
+    private Long id;
+
+    @TableField("parent_id")
+    @ApiModelProperty(value = "父节点")
+    private Integer parentId;
+
+    @TableField("type")
+    @ApiModelProperty(value = "菜单的响应动作类型,view表示网页类型,click表示点击类型,miniprogram表示小程序类型")
+    private String type;
+
+    @TableField("menu_name")
+    @ApiModelProperty(value = "菜单标题")
+    private String menuName;
+
+    @TableField("click_key")
+    @ApiModelProperty(value = "click等点击类型必须,菜单KEY值,用于消息接口推送,不超过128字节")
+    private String clickKey;
+
+    @TableField("url")
+    @ApiModelProperty(value = "view、miniprogram类型必须,网页 链接,用户点击菜单可打开链接,不超过1024字节。 type为miniprogram时,不支持小程序的老版本客户端将打开本url。")
+    private String url;
+
+    @TableField("media_id")
+    @ApiModelProperty(value = "media_id类型和view_limited类型必须,调用新增永久素材接口返回的合法media_id")
+    private String mediaId;
+
+    @TableField("appid")
+    @ApiModelProperty(value = "miniprogram类型必须,小程序的appid(仅认证公众号可配置)")
+    private String appid;
+
+    @TableField("page_path")
+    @ApiModelProperty(value = "miniprogram类型必须,小程序的页面路径")
+    private String pagePath;
+
+    @TableField("article_id")
+    @ApiModelProperty(value = "发布后获得的合法 article_id")
+    private String articleId;
+
+    @TableField("sort")
+    @ApiModelProperty(value = "排序")
+    private Integer sort;
+
+}

+ 13 - 0
twzd-service/src/main/java/com/miaxis/wx/mapper/WxMenuMapper.java

@@ -0,0 +1,13 @@
+package com.miaxis.wx.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.miaxis.wx.domain.WxMenu;
+
+/**
+ * 微信公众号菜单Mapper接口
+ * @author wwl
+ * @version 1.0
+ * @date 2021/10/26 10:12
+ */
+public interface WxMenuMapper extends BaseMapper<WxMenu> {
+}

+ 20 - 0
twzd-service/src/main/java/com/miaxis/wx/service/IWxGzhService.java

@@ -1,5 +1,9 @@
 package com.miaxis.wx.service;
 
+import com.alibaba.fastjson.JSONObject;
+
+import javax.servlet.http.HttpServletRequest;
+
 /**
  * @author wwl
  * @version 1.0
@@ -12,4 +16,20 @@ public interface IWxGzhService {
      * @return
      */
     String getGzhToken();
+
+    /**
+     * 查询微信菜单数据
+     * @return
+     */
+    JSONObject getWxMenuJson();
+
+
+    /**
+     * 微信推送消息处理
+     */
+    String handlePublicMsg(HttpServletRequest request);
+
+
+
+
 }

+ 171 - 3
twzd-service/src/main/java/com/miaxis/wx/service/impl/WxGzhServiceImpl.java

@@ -1,18 +1,28 @@
 package com.miaxis.wx.service.impl;
 
+import cn.hutool.json.XML;
+import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
 import com.miaxis.common.constant.Constants;
-import com.miaxis.common.core.redis.RedisCache;
+import com.miaxis.common.sign.VerifyUtil;
 import com.miaxis.common.utils.StringUtils;
+import com.miaxis.common.utils.wx.MessageUtil;
+import com.miaxis.feign.service.IWxMpService;
 import com.miaxis.feign.service.IWxSendService;
+import com.miaxis.wx.domain.WxMenu;
+import com.miaxis.wx.mapper.WxMenuMapper;
 import com.miaxis.wx.service.IWxGzhService;
+import com.qcloud.cos.COSClient;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.io.IOUtils;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.stereotype.Service;
 
-import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import java.util.HashMap;
+import java.util.List;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -28,7 +38,13 @@ public class WxGzhServiceImpl implements IWxGzhService {
 
     private final RedisTemplate redisTemplate;
 
-    private final  IWxSendService wxSendService;
+    private final IWxSendService wxSendService;
+
+    private final WxMenuMapper wxMenuMapper;
+
+    private final COSClient cosClient;
+
+    private final IWxMpService wxMpService;
 
     @Value("${app.appid}")
     private String appid;
@@ -37,6 +53,10 @@ public class WxGzhServiceImpl implements IWxGzhService {
     private String secret;
 
 
+    /**
+     * 获取微信公众号token
+     * @return
+     */
     @Override
     public String getGzhToken() {
         String token = "";
@@ -58,4 +78,152 @@ public class WxGzhServiceImpl implements IWxGzhService {
 
         return token;
     }
+
+
+    /**
+     * 查询微信菜单数据
+     * @return
+     */
+    @Override
+    public JSONObject getWxMenuJson() {
+
+        JSONObject menuJson = new JSONObject();
+        //获取父类
+        List<WxMenu> menuList = wxMenuMapper.selectByMap(new HashMap<String,Object>(){{
+            put("parent_id",0);
+        }});
+        JSONArray button=new JSONArray();
+        for(WxMenu menu : menuList){
+            if(!VerifyUtil.verifyString(menu.getType())){
+                JSONObject childButton = new JSONObject();
+                childButton.put("name", menu.getMenuName());
+                //父类
+                JSONArray sub_button = new JSONArray();
+                List<WxMenu> childMenuList = wxMenuMapper.selectByMap(new HashMap<String,Object>(){{
+                    put("parent_id",menu.getId());
+                }});
+
+                for (WxMenu childMenu : childMenuList) {
+                    sub_button.add(getChildMenuJson(childMenu));
+                }
+                childButton.put("sub_button", sub_button);
+                button.add(childButton);
+            }else{
+                button.add(getChildMenuJson(menu));
+            }
+        }
+
+        menuJson.put("button", button);
+        return menuJson;
+    }
+
+    private JSONObject getChildMenuJson(WxMenu menu){
+        JSONObject menuJson = new JSONObject();
+        menuJson.put("type", menu.getType());
+        menuJson.put("name", menu.getMenuName());
+        if(menu.getType().equals("view")){
+            menuJson.put("url", menu.getUrl());
+        }else if(menu.getType().equals("click")){
+            menuJson.put("key", menu.getClickKey());
+        }else if(menu.getType().equals("miniprogram")){
+            menuJson.put("url", menu.getUrl());
+            menuJson.put("key", menu.getClickKey());
+            menuJson.put("appid", menu.getAppid());
+            menuJson.put("pagepath", menu.getPagePath());
+        }
+        return menuJson;
+    }
+
+
+    /**
+     * 微信推送消息处理
+     * @param request
+     * @return
+     */
+    @Override
+    public String handlePublicMsg(HttpServletRequest request) {
+        try {
+            log.info("1....");
+            cn.hutool.json.JSONObject decryptMap = XML.toJSONObject(IOUtils.toString(request.getInputStream()));
+            log.info("2-----decryptMap------"+decryptMap);
+
+            //开发者微信号
+            String toUserName = decryptMap.getStr("ToUserName");
+            //发送方帐号(一个OpenID)
+            String fromUserName = decryptMap.getStr("FromUserName");
+            // 区分消息类型
+            String msgType = decryptMap.getStr("MsgType");
+            //回复消息(xml字符串)
+            String message = "";
+
+            // 普通消息
+            if (MessageUtil.MESSAGE_TEXT.equals(msgType)) { // 文本消息
+                log.info("2.1...");
+                String content = decryptMap.getStr("Content");
+                if (content.startsWith("你好")){
+                    message = MessageUtil.initText(fromUserName, toUserName, "你好");
+                }else if (content.startsWith("傻逼")){
+                    message = MessageUtil.initText(fromUserName, toUserName, "你才是煞笔");
+                }else {
+                    message = MessageUtil.initText(fromUserName, toUserName, "文本消息-默认回复信息");
+                }
+
+            } else if (MessageUtil.MESSAGE_IMAGE.equals(msgType)) { // 图片消息
+                log.info("2.2...");
+                message = MessageUtil.initText(fromUserName, toUserName, "抱歉,暂时无法识别图片信息!");
+            }else if (MessageUtil.MESSAGE_EVENT.equals(msgType)) { // 事件消息
+                log.info("3....");
+                // 区分事件推送
+                String event = decryptMap.getStr("Event");
+                if (MessageUtil.MESSAGE_SUBSCRIBE.equals(event)) { // 关注事件 或 扫描二维码关注事件
+                    log.info("3.1...");
+                    String content = "";
+
+                    //存在Ticket为扫码关注
+                    if (org.apache.commons.lang3.StringUtils.isNotEmpty(decryptMap.getStr("Ticket"))){
+                        log.info("3.1.2..");
+                        content = "扫描二维码关注事件";
+                        //根据Ticket  推送绑定信息到上级用户
+                        //从缓存中获取Ticket用户信息
+                    }else {
+                        content = "关注事件";
+                    }
+                    message = MessageUtil.initText(fromUserName, toUserName, content);
+
+                }  else if (MessageUtil.MESSAGE_UNSUBSCRIBE.equals(event)) { // 取消订阅事件
+                    // todo 处理取消订阅事件
+
+                } else if (MessageUtil.MESSAGE_SCAN.equals(event)) { // 已关注扫描二维码事件
+                    log.info("3.2...");
+                    message = MessageUtil.initText(fromUserName, toUserName, "已关注扫描二维码事件");
+                    
+                } else if (MessageUtil.MESSAGE_LOCATION.equals(event)) { // 上报地理位置事件
+                    // todo 处理上报地理位置事件
+
+                } else if (MessageUtil.MESSAGE_CLICK.equals(event)) { // 点击菜单拉取消息时的事件推送事件
+                    //判断事件KEY值,与自定义菜单接口中KEY值对应
+                    if ("generateTicket".equals(decryptMap.get("EventKey"))){ //获取分销二维码
+                        message = MessageUtil.initNews(fromUserName,
+                                toUserName,
+                                "图文消息信息",
+                                "title",
+                                "描述",
+                                "https://twzd-1305573081.cos.ap-shanghai.myqcloud.com/twzd-test/testTicket/2021/10/25/1635154145629.png",
+                                "www.baidu.com"
+                        );
+                    }
+
+                } else if (MessageUtil.MESSAGE_VIEW.equals(event)) { // 点击菜单跳转链接时的事件推送
+                    // todo 处理点击菜单跳转链接时的事件推送
+                }
+            }
+            
+            return message;
+        } catch (Exception e) {
+            log.error("处理微信公众号请求信息,失败", e);
+        }
+        return null;
+    }
+
+
 }