Java对接微信公众平台详解

1.公众平台概述

1.1 公众平台概述

微信公众平台是运营者通过公众号为微信用户提供资讯和服务的平台,而公众平台开发接口则是提供服务的基础,开发者在公众平台网站中创建公众号、获取接口权限后,实现后台接口对接微信公众号的。

官方文档:
https://developers.weixin.qq.com/doc/offiaccount/Getting_Started/Overview.html

1.2 入门指引

为了降低门槛,弥补不足,我们编写了《开发者指引》来讲解微信开放平台的基础常见功能,旨在帮助大家入门微信开放平台的开发者模式。

已熟知接口使用或有一定公众平台开发经验的开发者,请直接跳过本文。这篇文章不会给你带来厉害的编码技巧亦或接口的深层次讲解。对于现有接口存在的疑问,可访问 #公众号社区 发帖交流、联系腾讯客服或使用微信反馈。

对接公众平台与对接开放平台相比比较简单,整个过程只需要配置好开发配置,通过AppID与AppSecret获取token即可,后续所有接口都是通过token调用。

开发者指引:
https://developers.weixin.qq.com/doc/offiaccount/Getting_Started/Getting_Started_Guide.html

2.对接流程

2.1 接入概述

接入微信公众平台开发,开发者需要按照如下步骤完成:
1、填写服务器配置
2、验证服务器地址的有效性
3、依据接口文档实现业务逻辑

注意:
1.填写服务器配置前提是注册公众号,并拥有相关的权限,注册不同公众号类型,是否认证权限不同。
2.配置服务器还需要能够对接外网回调,这个需要提前内网穿透(Sunny-Ngrok使用教程),或者云服务器也可。

不同类型公众号权限说明:
https://developers.weixin.qq.com/doc/offiaccount/Getting_Started/Explanation_of_interface_privileges.html

全局返回码说明:
https://developers.weixin.qq.com/doc/offiaccount/Getting_Started/Global_Return_Code.html

开发者工具
https://mp.weixin.qq.com/cgi-bin/frame?t=advanced/dev_tools_frame&nav=10049&token=809046757&lang=zh_CN
在这里插入图片描述

2.2 填写服务器配置

1.配置说明
登录微信公众平台官网后,在公众平台官网的开发-基本设置页面,勾选协议成为开发者,点击“修改配置”按钮,填写服务器地址(URL)、Token和EncodingAESKey,其中URL是开发者用来接收微信消息和事件的接口URL。Token可由开发者可以任意填写,用作生成签名(该Token会和接口URL中包含的Token进行比对,从而验证安全性)。EncodingAESKey由开发者手动填写或随机生成,将用作消息体加解密密钥。

同时,开发者可选择消息加解密方式:明文模式、兼容模式和安全模式。模式的选择与服务器配置在提交后都会立即生效,请开发者谨慎填写及选择。加解密方式的默认状态为明文模式,选择兼容模式和安全模式需要提前配置好相关加解密代码,详情请参考消息体签名及加解密部分的文档

填写服务器配置说明:
https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html

2.配置信息状态
在这里插入图片描述
3.注意事项

服务器配置开启状态则菜单配置不可用,关闭状态才能后台配置,也就是接口调用与后台配置互斥。

因为个人订阅号无法认证,所以可以使用添加测试账号。
路径:公众号 - 设置与开发 - 开发者工具 – 公众平台测试账号
https://mp.weixin.qq.com/debug/cgi-bin/sandboxinfo?action=showinfo&t=sandbox/index
在这里插入图片描述

2.3 接口域名说明

公众平台接口域名说明
开发者可以根据自己的服务器部署情况,选择最佳的接入域名(延时更低,稳定性更高)。除此之外,可以将其他接入域名用作容灾用途,当网络链路发生故障时,可以考虑选择备用域名来接入。请开发者使用域名进行API接口请求,不要使用IP作为访问。若有需要开通网络策略,开发者可以从获取微信服务器IP地址定期获取最新的IP信息。

通用域名(api.weixin.qq.com),使用该域名将访问官方指定就近的接入点;
通用异地容灾域名(api2.weixin.qq.com),当上述域名不可访问时可改访问此域名;
上海域名(sh.api.weixin.qq.com),使用该域名将访问上海的接入点;
深圳域名(sz.api.weixin.qq.com),使用该域名将访问深圳的接入点;
香港域名(hk.api.weixin.qq.com),使用该域名将访问香港的接入点。

公众平台接口域名说明:
https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Interface_field_description.html

2.4 获取Access token

access_token是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_token。开发者需要进行妥善保存。access_token的存储至少要保留512个字符空间。access_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的access_token失效。

获取Access token:
https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html

3.项目实现

3.1 创建项目

创建springboot项目,代码还有很多可以优化的地方,比如消息可以做策略,代码中的枚举类型,常量,授权信息可以放缓存等等,因为这里只做测试,可以结合测试自行优化。
为了方便测试,这些授权参数放到集合内存里面了,所以每次重启丢失,后期是要优化到redis缓存持久化的,验证票据有效期12小时所以为了测试可以写死,其他的从内存取就可以了。

另外可以有许多开源的微信开发框架可以参考学习使用,这里依赖引入微信开发框架weixin-Java-tools binarywang只是为了学习研究,并为使用。
weixin-Java-tools:https://gitee.com/itwu/weixin-java-tools#https://mp.weixin.qq.com/s/nIk_xOf6dxkhKfqq830Cuw

3.2 项目依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.zrj</groupId>
    <artifactId>wechat</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>wechat</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--数据库连接诶-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.4</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <!--常用工具-->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>30.1.1-jre</version>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.6.3</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.72</version>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.6</version>
        </dependency>
        <!--swagger2依赖-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.6.1</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.6.1</version>
        </dependency>
        <!--微信加解密-->
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
            <version>1.9</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
        </dependency>
        <dependency>
            <groupId>org.dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>2.1.1</version>
        </dependency>
        <!--字段校验-->
        <dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
            <version>2.0.1.Final</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>6.0.13.Final</version>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpcore</artifactId>
            <version>4.4.14</version>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.13</version>
        </dependency>
        <!-- 微信开发框架weixin-Java-tools binarywang-->
        <dependency>
            <groupId>com.github.binarywang</groupId>
            <artifactId>weixin-java-common</artifactId>
            <version>4.1.0</version>
        </dependency>
        <dependency>
            <groupId>com.github.binarywang</groupId>
            <artifactId>weixin-java-mp</artifactId>
            <version>4.1.0</version>
        </dependency>
        <dependency>
            <groupId>com.github.binarywang</groupId>
            <artifactId>weixin-java-open</artifactId>
            <version>4.1.0</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>


3.3 项目配置

这里暂时没用到

3.4 加解密工具

消息加解密说明:
https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/api/Before_Develop/Message_encryption_and_decryption.html

加解密工具下载地址:https://res.wx.qq.com/op_res/-serEQ6xSDVIjfoOHcX78T1JAYX-pM_fghzfiNYoD8uHVd3fOeC0PC_pvlg4-kmP

1.下载解压后根据对应的语言选择对应的工具类,直接打成jar包放入私服引用或者直接放入项目引用即可。
2.需要导入架包commons-codec-1.9(或commons-codec-1.8等其他版本)
3.这里需要注意的是加解密的时候与公众号加解密参数不同,因为返回的xml不一样,如果使用公众号的加解密会出现空指针,需要根据返回结果调整,公众号加密后xml是ToUser与Encrypt标签,开放平台的返回是AppId与Encrypt标签,其中Encrypt是加密内容。

依赖替换

/**
 * 针对org.apache.commons.codec.binary.Base64,
 * 需要导入架包commons-codec-1.9(或commons-codec-1.8等其他版本)
 * 官方下载地址:http://commons.apache.org/proper/commons-codec/download_codec.cgi
 */

/**
 * 提供接收和推送给公众平台消息的加解密接口(UTF8编码的字符串).
 * <ol>
 * 	<li>第三方回复加密消息给公众平台</li>
 * 	<li>第三方收到公众平台发送的消息,验证消息的安全性,并对消息进行解密。</li>
 * </ol>
 * 说明:异常java.security.InvalidKeyException:illegal Key Size的解决方案
 * <ol>
 * 	<li>在官方网站下载JCE无限制权限策略文件(JDK7的下载地址:
 *      http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html</li>
 * 	<li>下载后解压,可以看到local_policy.jar和US_export_policy.jar以及readme.txt</li>
 * 	<li>如果安装了JRE,将两个jar文件放到%JRE_HOME%\lib\security目录下覆盖原来的文件</li>
 * 	<li>如果安装了JDK,将两个jar文件放到%JDK_HOME%\jre\lib\security目录下覆盖原来文件</li>
 * </ol>
 */

加解密调整类XMLParse

/**
 * 对公众平台发送给公众账号的消息加解密示例代码.
 *
 * @copyright Copyright (c) 1998-2014 Tencent Inc.
 */

// ------------------------------------------------------------------------

package com.zrj.wechat.aes;

import java.io.StringReader;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

/**
 * 提供提取消息格式中的密文及生成回复消息格式的接口.
 */
class XMLParse {

    /**
     * 提取出xml数据包中的加密消息
     *
     * @param xmltext 待提取的xml字符串
     * @return 提取出的加密消息字符串
     * @throws AesException
     */
    public static Object[] extract(String xmltext) throws AesException {
        Object[] result = new Object[3];
        try {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
            dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
            dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
            dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
            dbf.setXIncludeAware(false);
            dbf.setExpandEntityReferences(false);
            DocumentBuilder db = dbf.newDocumentBuilder();
            StringReader sr = new StringReader(xmltext);
            InputSource is = new InputSource(sr);
            Document document = db.parse(is);

            Element root = document.getDocumentElement();
            //公众平台解密字段
            NodeList nodelist1 = root.getElementsByTagName("Encrypt");
            NodeList nodelist2 = root.getElementsByTagName("ToUserName");

            //开放平台解密字段
            //NodeList nodelist1 = root.getElementsByTagName("Encrypt");
            //NodeList nodelist2 = root.getElementsByTagName("AppId");
            result[0] = 0;
            result[1] = nodelist1.item(0).getTextContent();
            result[2] = nodelist2.item(0).getTextContent();
            return result;
        } catch (Exception e) {
            e.printStackTrace();
            throw new AesException(AesException.ParseXmlError);
        }
    }

    /**
     * 开放平台,提取出xml数据包中的加密消息
     *
     * @param xmltext 待提取的xml字符串
     * @return 提取出的加密消息字符串
     * @throws AesException
     */
    public static Object[] extractOP(String xmltext) throws AesException {
        Object[] result = new Object[3];
        try {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
            dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
            dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
            dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
            dbf.setXIncludeAware(false);
            dbf.setExpandEntityReferences(false);
            DocumentBuilder db = dbf.newDocumentBuilder();
            StringReader sr = new StringReader(xmltext);
            InputSource is = new InputSource(sr);
            Document document = db.parse(is);

            Element root = document.getDocumentElement();
            NodeList nodelist1 = root.getElementsByTagName("Encrypt");
            //NodeList nodelist2 = root.getElementsByTagName("ToUserName");
            NodeList nodelist2 = root.getElementsByTagName("AppId");
            result[0] = 0;
            result[1] = nodelist1.item(0).getTextContent();
            result[2] = nodelist2.item(0).getTextContent();
            return result;
        } catch (Exception e) {
            e.printStackTrace();
            throw new AesException(AesException.ParseXmlError);
        }
    }

    /**
     * 生成xml消息
     *
     * @param encrypt   加密后的消息密文
     * @param signature 安全签名
     * @param timestamp 时间戳
     * @param nonce     随机字符串
     * @return 生成的xml字符串
     */
    public static String generate(String encrypt, String signature, String timestamp, String nonce) {

        String format = "<xml>\n" + "<Encrypt><![CDATA[%1$s]]></Encrypt>\n"
                + "<MsgSignature><![CDATA[%2$s]]></MsgSignature>\n"
                + "<TimeStamp>%3$s</TimeStamp>\n" + "<Nonce><![CDATA[%4$s]]></Nonce>\n" + "</xml>";
        return String.format(format, encrypt, signature, timestamp, nonce);

    }
}

3.5 代码实现

WechatUtils

package com.zrj.wechat.utils;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;

import java.io.ByteArrayInputStream;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

/**
 * 微信工具类
 *
 * @author zrj
 * @since 2021/9/22
 **/
@Slf4j
public class WechatUtils {

    private static final String AUTHORIZER_ACCESS_TOKEN = "ACCESS_TOKEN";
    private static final String COMPONENT_ACCESS_TOKEN = "COMPONENT_ACCESS_TOKEN";

    /**
     * 获取授权token
     */
    public static String getAuthorizerAccessToken() {
        // 从缓存获取授权信息,使用授权码获取授权信息
        String authorizerAccessToken = "";
        return authorizerAccessToken;
    }

    /**
     * 替换请求URL的COMPONENT_ACCESS_TOKEN
     */
    public static String replaceComponentAccessToken(String url, String componentAccessToken) {
        if (StrUtil.isEmpty(componentAccessToken)) {
            return url;
        }
        return url.replaceAll(COMPONENT_ACCESS_TOKEN, componentAccessToken);
    }

    /**
     * 替换请求URL的AUTHORIZER_ACCESS_TOKEN
     */
    public static String replaceAuthorizerAccessToken(String url, String authorizerAccessToken) {
        return url.replaceAll(AUTHORIZER_ACCESS_TOKEN, authorizerAccessToken);
    }

    /**
     * 校验响应信息
     */
    public static boolean validWechatResult(JSONObject jsonObject) {
        if (jsonObject == null) {
            log.error("执行失败,响应结果为空,jsonObject:{}", jsonObject);
            return false;
        }

        int errcode = jsonObject.getIntValue("errcode");
        String errmsg = jsonObject.getString("errmsg");
        if (0 != errcode) {
            log.error("执行失败,响应信息 errcode:{} errmsg:{}", errcode, errmsg);
            return false;
        }
        log.error("执行成功,响应信息 errcode:{} errmsg:{}", errcode, errmsg);
        return true;
    }

    /**
     * 处理Get请求
     *
     * @param url
     * @return
     */
    public static JSONObject doGetstr(String url) {
        CloseableHttpClient httpclient = HttpClients.createDefault();
        HttpGet httpGet = new HttpGet(url);
        JSONObject jsonObject = null;
        try {
            CloseableHttpResponse response = httpclient.execute(httpGet);
            HttpEntity entity = response.getEntity();
            if (entity != null) {
                String result = EntityUtils.toString(entity);
                //jsonObject =JSONObject.fromObject(result);
                jsonObject = JSONObject.parseObject(result);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return jsonObject;
    }

    /**
     * 处理Post请求
     *
     * @param url    请求url
     * @param outStr 请求字符串
     * @return JSONObject
     */
    public static JSONObject doPoststr(String url, String outStr) {
        CloseableHttpClient httpclient = HttpClients.createDefault();
        HttpPost httpPost = new HttpPost(url);
        JSONObject jsonObject = null;
        try {
            httpPost.setEntity(new StringEntity(outStr, "utf-8"));
            CloseableHttpResponse response = httpclient.execute(httpPost);
            String result = EntityUtils.toString(response.getEntity(), "utf-8");
            //jsonObject =JSONObject.fromObject(result);
            jsonObject = JSONObject.parseObject(result);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return jsonObject;
    }

    /**
     * xml转map
     *
     * @param xml 要转换的xml字符串
     * @return 转换成map后返回结果
     * @throws Exception
     */
    public static Map<String, String> xmlToMap(String xml) throws Exception {
        Map<String, String> respMap = new HashMap<>(16);
        SAXReader reader = new SAXReader();
        Document doc = reader.read(new ByteArrayInputStream(xml.getBytes("utf-8")));
        Element root = doc.getRootElement();
        xmlToMap(root, respMap);
        return respMap;
    }

    /**
     * map对象转行成xml
     *
     * @param map 集合
     * @return
     * @throws Exception
     */
    public static String mapToXml(Map<String, Object> map) throws Exception {
        Document d = DocumentHelper.createDocument();
        Element root = d.addElement("xml");
        mapToXml(root, map);
        StringWriter sw = new StringWriter();
        XMLWriter xw = new XMLWriter(sw);
        xw.setEscapeText(false);
        xw.write(d);
        return sw.toString();
    }

    /**
     * 递归转换
     *
     * @param root Element
     * @param map  Map
     * @return
     */
    private static Element mapToXml(Element root, Map<String, Object> map) {
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            if (entry.getValue() instanceof Map) {
                Element element = root.addElement(entry.getKey());
                mapToXml(element, (Map<String, Object>) entry.getValue());
            } else {
                root.addElement(entry.getKey()).addText(entry.getValue().toString());
            }
        }
        return root;
    }

    /**
     * 递归转换
     *
     * @param tmpElement
     * @param respMap
     * @return
     */
    private static Map<String, String> xmlToMap(Element tmpElement, Map<String, String> respMap) {
        if (tmpElement.isTextOnly()) {
            respMap.put(tmpElement.getName(), tmpElement.getText());
            return respMap;
        }
        Iterator<Element> eItor = tmpElement.elementIterator();
        while (eItor.hasNext()) {
            Element element = eItor.next();
            xmlToMap(element, respMap);
        }
        return respMap;
    }

}

WechatMpService

package com.zrj.wechat.service;

import com.zrj.wechat.entity.Response;

/**
 * 微信公众平台接口
 *
 * @author zrj
 * @since 2021/9/23
 **/
public interface WechatMpService {
    /**
     * 微信公众平台消息和事件推送接收服务
     *
     * @param signature 微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数
     * @param timestamp 时间戳
     * @param nonce     随机数
     * @param echostr   随机字符串
     * @param postData  消息体
     * @return 如果获得只需要返回 SUCCESS
     */
    String event(String signature, String timestamp, String nonce, String echostr, String encryptType, String msgSignature, String openid, String postData);

    /**
     * 微信公众平台获取AccessToken
     */
    Response getAccessToken();

    /**
     * 获取公众号的自动回复规则
     */
    Response getCurrentAutoreplyInfo();

    /**
     * 获取微信服务器IP地址
     */
    Response getApiDomainIp();

    /**
     * 网络检测
     */
    Response check();

    /**
     * 获取成功发布列表
     */
    Response batchGetFreePublish();

    /**
     * 自定义菜单/获取自定义菜单配置(获取默认菜单和全部个性化菜单信息)
     */
    Response<Object> getMenu();

    /**
     * 自定义菜单/获取当前自定义菜单(网站功能发布菜单和过API调用设置的菜单)
     */
    Response<Object> getCurrentMenu();

    /**
     * 自定义菜单/创建菜单
     */
    Response createMenu();

    /**
     * 自定义菜单/删除菜单
     */
    Response deleteMenu();
}

WechatMpServiceImpl

package com.zrj.wechat.service.impl;

import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.zrj.wechat.aes.WXBizMsgCrypt;
import com.zrj.wechat.entity.*;
import com.zrj.wechat.service.WechatMpService;
import com.zrj.wechat.utils.WechatUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;

/**
 * 微信公众平台接口实现类
 *
 * @author zrj
 * @since 2021/9/27
 **/
@Slf4j
@Service("WechatMpServiceImpl")
public class WechatMpServiceImpl implements WechatMpService {

    //第三方用户唯一凭证
    public static final String APP_ID = "wx1xxxxxx441";
    //第三方用户唯一凭证密钥,即appSecret
    public static final String APPSECRET = "b3ecxxxxxxxxxxxx900205d6feae7c";
    //公众号 令牌(Token),可以优化到配置中心,这个是自定义的,与公众平台配置一致即可
    public static final String MP_TOKEN = "zrj";
    //公众号 消息加解密密钥 EncodingAESKey
    public static final String MP_ENCODING_AES_KEY = "tVNZZP2WuEJxxxxxxAcnZxUAYHvKbl6";

    //获取access_token填写client_credential
    public static final String GRANT_TYPE = "client_credential";
    //获取access_token接口地址
    public static final String GET_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token";
    //TODO 令牌access_token,可以放缓存Redis,方便存取,定时刷新
    public static final String ACCESS_TOKEN = "50_WeVdh9Flt0V5r040BB4M0jr7Db1rfts6YjbZELiSFrcworPhhSWYl6-FfDQqRywN6R-ydN5ejTXwmk7f5u6xKUy2_p1EX3Ku9omS-xfAOgjmGczWtgqFf8BZ-ykWA8gph23wWuSgOjuFLvzVENJiADALUQ";

    //获取公众号的自动回复规则
    public static final String GET_CURRENT_AUTOREPLY_INFO = "https://api.weixin.qq.com/cgi-bin/get_current_autoreply_info";
    //获取微信服务器IP地址
    public static final String GET_API_DOMAIN_IP = "https://api.weixin.qq.com/cgi-bin/get_api_domain_ip";
    //网络检测
    public static final String CHECK = "https://api.weixin.qq.com/cgi-bin/callback/check?access_token=ACCESS_TOKEN";
    //发布能力 获取成功发布列表
    public static final String BATCH_GET_FREE_PUBLISH = "https://api.weixin.qq.com/cgi-bin/freepublish/batchget?access_token=ACCESS_TOKEN";

    //获取自定义菜单配置(获取默认菜单和全部个性化菜单信息),自定义菜单查询接口则仅能查询到使用API设置的菜单配置 GET
    private static final String getMenuUrl = "https://api.weixin.qq.com/cgi-bin/menu/get";
    //当前使用的自定义菜单(网站功能发布菜单和过API调用设置的菜单) GET
    private static final String getCurrentMenuUrl = "https://api.weixin.qq.com/cgi-bin/get_current_selfmenu_info";
    //创建菜单 POST
    private static final String createMenuUrl = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN";
    //删除菜单 GET
    private static final String deleteMenuUrl = "https://api.weixin.qq.com/cgi-bin/menu/delete";

    /**
     * 微信公众平台消息和事件推送接收服务
     * 微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次
     * 如果开发者希望增强安全性,可以在开发者中心处开启消息加密,这样,用户发给公众号的消息以及公众号被动回复用户消息都会继续加密,详见被动回复消息加解密说明。
     * 假如服务器无法保证在五秒内处理并回复,必须做出下述回复,这样微信服务器才不会对此作任何处理,并且不会发起重试(这种情况下,可以使用客服消息接口进行异步回复),否则,将出现严重的错误提示。详见下面说明:
     * 1、直接回复success(推荐方式) 2、直接回复空串(指字节长度为0的空字符串,而不是XML结构体中content字段的内容为空)
     * 一旦遇到以下情况,微信都会在公众号会话中,向用户下发系统提示“该公众号暂时无法提供服务,请稍后再试”:
     * 1、开发者在5秒内未回复任何内容 2、开发者回复了异常数据,比如JSON数据等
     * 另外,请注意,回复图片(不支持gif动图)等多媒体消息时需要预先通过素材管理接口上传临时素材到微信服务器,可以使用素材管理中的临时素材,也可以使用永久素材。
     * 注意:测试中3秒故障提示,“该公众号暂时无法提供服务,请稍后再试”,直接返回"success",无异常。
     *
     * @param signature    微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数
     * @param timestamp    时间戳
     * @param nonce        随机数
     * @param echostr      随机字符串
     * @param encryptType
     * @param msgSignature
     * @param openid
     * @param postData     消息体
     * @return 如果获得只需要返回 SUCCESS
     */
    @Override
    public String event(String signature, String timestamp, String nonce, String echostr, String encryptType, String msgSignature, String openid, String postData) {
        log.info("【微信公众平台消息事件接收服务】请求参数:signature:【{}】,timestamp:【{}】,nonce:【{}】,echostr:【{}】,encryptType:【{}】,msgSignature:【{}】,openid:【{}】,postData:【{}】", signature, timestamp, nonce, echostr, encryptType, msgSignature, openid, "");
        System.out.println(postData);

        //1.配置url验证
        if (StrUtil.isEmpty(postData)) {
            log.info("【微信公众平台消息事件接收服务】消息体为空直接返回验证成功,返回随机数,echostr:【{}】", echostr);
            return echostr;
        }

        //2.接收事件消息
        try {
            //方式一
            //假如服务器无法保证在五秒内处理并回复,必须做出下述回复,这样微信服务器才不会对此作任何处理,并且不会发起重试(这种情况下,可以使用客服消息接口进行异步回复),否则,将出现严重的错误提示。
            //1、直接回复success(推荐方式) 2、直接回复空串(指字节长度为0的空字符串,而不是XML结构体中content字段的内容为空)
            //测试中3秒故障提示,“该公众号暂时无法提供服务,请稍后再试”,直接返回"success",无异常
            //if (true) {
            //    return "success";
            //}

            //方式二:正常返回,超时会提示用户:该公众号提供的服务出现故障,请稍后再试
            //这个类是微信官网提供的解密类,需要用到消息校验Token 消息加密Key和服务平台appid
            WXBizMsgCrypt pc = new WXBizMsgCrypt(MP_TOKEN, MP_ENCODING_AES_KEY, APP_ID);
            //加密模式:需要解密
            String xml = pc.decryptMsg(msgSignature, timestamp, nonce, postData);
            // 将xml转为map
            Map<String, String> resultMap = WechatUtils.xmlToMap(xml);
            log.info("【微信公众平台消息事件接收服务】解密后消息:【{}】", resultMap);

            //构建消息体返回,回复文本消息
            Map<String, Object> respMsgMap = new HashMap<>(16);
            //MsgType类型说明:text:回复文本消息,image:回复图片消息,voice:回复语音消息,video:回复视频消息,music:回复音乐消息,news:回复图文消息
            String msgType = resultMap.get("MsgType");
            if ("text".equals(msgType)) {
                log.info("【微信公众平台消息事件接收服务】文本消息类型");
                respMsgMap.put("ToUserName", "<![CDATA[" + resultMap.get("ToUserName") + "]]>");
                respMsgMap.put("FromUserName", "<![CDATA[" + resultMap.get("FromUserName") + "]]>");
                respMsgMap.put("CreateTime", resultMap.get("CreateTime"));
                respMsgMap.put("MsgType", "<![CDATA[" + resultMap.get("MsgType") + "]]>");
                respMsgMap.put("Content", "<![CDATA[感谢你这么好看,还关注我[玫瑰][玫瑰][玫瑰]]]>");
            } else {
                log.info("【微信公众平台消息事件接收服务】其他类型消息,暂不处理");
                return "success";
            }

            String respMsgXml = WechatUtils.mapToXml(respMsgMap);
            log.info("【微信公众平台消息事件接收服务成功】消息回复:{}", respMsgXml);
            return respMsgXml;
        } catch (Exception e) {
            log.info("【微信公众平台消息事件接收服务】事件接收异常:", e);
            return "success";
        }
    }

    /**
     * 微信公众平台获取AccessToken
     * ?grant_type=client_credential&appid=APPID&secret=APPSECRET
     */
    @Override
    public Response getAccessToken() {
        log.info("【微信公众平台获取AccessToken服务】");

        try {
            //构建请求参数
            Map<String, Object> paramMap = new HashMap<>(16);
            paramMap.put("grant_type", GRANT_TYPE);
            paramMap.put("appid", APP_ID);
            paramMap.put("secret", APPSECRET);
            log.info("【微信公众平台获取AccessToken接口】请求参数:【{}】,请求地址:【{}】", JSON.toJSONString(paramMap), GET_ACCESS_TOKEN_URL);

            //执行请求,获取结果
            String result = HttpUtil.get(GET_ACCESS_TOKEN_URL, paramMap);
            JSONObject resultJsonObject = JSON.parseObject(result);

            log.info("【微信公众平台获取AccessToken接口】响应结果:【{}】", resultJsonObject);
            return Response.success("", resultJsonObject);
        } catch (Exception e) {
            log.info("【微信公众平台消息事件接收服务】事件接收异常:", e);
            return Response.fail("微信公众平台消息事件接收服务异常");
        }
    }

    /**
     * 获取公众号的自动回复规则
     * ?access_token=ACCESS_TOKEN
     */
    @Override
    public Response getCurrentAutoreplyInfo() {
        log.info("【获取公众号的自动回复规则】");

        try {
            //构建请求参数
            Map<String, Object> paramMap = new HashMap<>(16);
            paramMap.put("access_token", ACCESS_TOKEN);
            log.info("【获取公众号的自动回复规则】请求参数:【{}】,请求地址:【{}】", JSON.toJSONString(paramMap), GET_ACCESS_TOKEN_URL);

            //执行请求,获取结果
            String result = HttpUtil.get(GET_CURRENT_AUTOREPLY_INFO, paramMap);
            JSONObject resultJsonObject = JSON.parseObject(result);

            log.info("【获取公众号的自动回复规则】响应结果:【{}】", resultJsonObject);
            return Response.success("", resultJsonObject);
        } catch (Exception e) {
            log.info("【获取公众号的自动回复规则】处理异常:", e);
            return Response.fail("获取公众号的自动回复规则异常");
        }
    }

    /**
     * 获取微信服务器IP地址
     * https://api.weixin.qq.com/cgi-bin/get_api_domain_ip?access_token=ACCESS_TOKEN
     */
    @Override
    public Response getApiDomainIp() {
        log.info("【微信公众平台获取微信服务器IP地址】");

        try {
            //构建请求参数
            Map<String, Object> paramMap = new HashMap<>(16);
            paramMap.put("access_token", ACCESS_TOKEN);
            log.info("【微信公众平台获取微信服务器IP地址】请求参数:【{}】,请求地址:【{}】", JSON.toJSONString(paramMap), GET_API_DOMAIN_IP);

            //执行请求,获取结果
            String result = HttpUtil.get(GET_API_DOMAIN_IP, paramMap);
            JSONObject resultJsonObject = JSON.parseObject(result);

            log.info("【微信公众平台获取微信服务器IP地址】响应结果:【{}】", resultJsonObject);
            return Response.success("", resultJsonObject);
        } catch (Exception e) {
            log.info("【微信公众平台获取微信服务器IP地址】处理异常:", e);
            return Response.fail("微信公众平台获取微信服务器IP地址异常");
        }
    }

    /**
     * 网络检测
     */
    @Override
    public Response check() {
        log.info("【微信公众平台网络检测服务】");

        try {
            //构建请求地址 access_token 	是 	调用接口凭证
            String getCheckUrl = CHECK.replaceAll("ACCESS_TOKEN", ACCESS_TOKEN);

            //构建请求参数
            Map<String, Object> paramMap = new HashMap<>(16);
            //action是 	执行的检测动作,允许的值:dns(做域名解析)、ping(做ping检测)、all(dns和ping都做)
            paramMap.put("action", "all");
            //check_operator是 	指定平台从某个运营商进行检测,允许的值:CHINANET(电信出口)、UNICOM(联通出口)、CAP(腾讯自建出口)、DEFAULT(根据ip来选择运营商)
            paramMap.put("check_operator", "DEFAULT");
            String jsonString = JSON.toJSONString(paramMap);
            log.info("【微信公众平台网络检测】请求参数:【{}】,请求地址:【{}】", jsonString, getCheckUrl);

            //执行请求,获取结果
            String result = HttpUtil.post(getCheckUrl, jsonString);
            JSONObject resultJsonObject = JSON.parseObject(result);

            log.info("【微信公众平台网络检测】响应结果:【{}】", resultJsonObject);
            return Response.success("", resultJsonObject);
        } catch (Exception e) {
            log.info("【微信公众平台网络检测】处理异常:", e);
            return Response.fail("微信公众平台网络检测异常");
        }
    }

    /**
     * 获取成功发布列表
     */
    @Override
    public Response batchGetFreePublish() {
        log.info("【微信公众平台获取成功发布列表】");

        try {
            //构建请求地址 access_token 	是 	调用接口凭证
            String getFreePublishUrl = BATCH_GET_FREE_PUBLISH.replaceAll("ACCESS_TOKEN", ACCESS_TOKEN);

            //构建请求参数
            Map<String, Object> paramMap = new HashMap<>(16);
            //offset 是 	从全部素材的该偏移位置开始返回,0表示从第一个素材返回
            paramMap.put("offset", 0);
            //count 是 	返回素材的数量,取值在1到20之间
            paramMap.put("count", 20);
            //no_content 否 	1 表示不返回 content 字段,0 表示正常返回,默认为 0
            paramMap.put("no_content", 0);
            String jsonString = JSON.toJSONString(paramMap);
            log.info("【微信公众平台获取成功发布列表】请求参数:【{}】,请求地址:【{}】", jsonString, getFreePublishUrl);

            //执行请求,获取结果
            String result = HttpUtil.post(getFreePublishUrl, jsonString);
            JSONObject resultJsonObject = JSON.parseObject(result);

            log.info("【微信公众平台获取成功发布列表】响应结果:【{}】", resultJsonObject);
            return Response.success("", resultJsonObject);
        } catch (Exception e) {
            log.info("【微信公众平台获取成功发布列表】处理异常:", e);
            return Response.fail("微信公众平台获取成功发布列表异常");
        }
    }

    /**
     * 自定义菜单/获取自定义菜单配置
     * 一期默认没有个性化菜单
     * 【获取自定义菜单配置】响应结果:【{"errcode":48001,"errmsg":"api unauthorized rid: 6174118f-158c1dc7-5cf547b1"}】
     */
    @Override
    public Response<Object> getMenu() {
        log.info("【微信公众平台获取自定义菜单配置】");
        try {
            //构建请求参数
            Map<String, Object> paramMap = new HashMap<>(16);
            paramMap.put("access_token", ACCESS_TOKEN);
            log.info("【微信公众平台获取自定义菜单配置】请求参数:【{}】,请求地址:【{}】", JSON.toJSONString(paramMap), getMenuUrl);

            //执行请求,获取结果
            String result = HttpUtil.get(getMenuUrl, paramMap);
            JSONObject resultJsonObject = JSON.parseObject(result);

            log.info("【微信公众平台获取自定义菜单配置】响应结果:【{}】", resultJsonObject);
            return Response.success("", resultJsonObject);
        } catch (Exception e) {
            log.info("【微信公众平台获取自定义菜单配置】处理异常:", e);
            return Response.fail("获取自定义菜单配置异常");
        }
    }

    /**
     * 自定义菜单/获取当前自定义菜单(网站功能发布菜单和过API调用设置的菜单)
     */
    @Override
    public Response<Object> getCurrentMenu() {
        log.info("【微信公众平台获取当前自定义菜单】");
        try {
            //构建请求参数
            Map<String, Object> paramMap = new HashMap<>(16);
            paramMap.put("access_token", ACCESS_TOKEN);
            log.info("【微信公众平台获取当前自定义菜单】请求参数:【{}】,请求地址:【{}】", JSON.toJSONString(paramMap), getCurrentMenuUrl);

            //执行请求,获取结果
            String result = HttpUtil.get(getCurrentMenuUrl, paramMap);
            JSONObject resultJsonObject = JSON.parseObject(result);

            log.info("【微信公众平台获取当前自定义菜单】响应结果:【{}】", resultJsonObject);
            return Response.success("", resultJsonObject);
        } catch (Exception e) {
            log.info("【微信公众平台获取当前自定义菜单】处理异常:", e);
            return Response.fail("微信公众平台获取当前自定义菜单异常");
        }
    }

    /**
     * 自定义菜单/创建菜单
     * 目前支持click与view及混合
     */
    @Override
    public Response createMenu() {
        log.info("【微信公众平台自定义菜单/创建菜单】");
        try {
            //构建请求地址 access_token 	是 	调用接口凭证
            String getCreateMenuUrl = createMenuUrl.replaceAll("ACCESS_TOKEN", ACCESS_TOKEN);

            //构建请求对象
            String jsonString = JSON.toJSONString(buildMenuResp());
            log.info("【微信公众平台自定义菜单/创建菜单】请求参数:【{}】,请求地址:【{}】", jsonString, getCreateMenuUrl);

            //执行请求,获取结果
            String result = HttpUtil.post(getCreateMenuUrl, jsonString);
            JSONObject resultJsonObject = JSON.parseObject(result);
            log.info("【微信公众平台自定义菜单/创建菜单】响应结果:【{}】", resultJsonObject);

            if (0 != resultJsonObject.getIntValue("errcode")) {
                log.info("【微信公众平台自定义菜单/创建菜单】创建菜单失败");
                return Response.fail("自定义菜单/创建菜单失败");
            }

            log.info("【微信公众平台自定义菜单/创建菜单成功】");
            return Response.success("自定义菜单/创建菜单成功", resultJsonObject);
        } catch (Exception e) {
            log.info("【微信公众平台自定义菜单/创建菜单】处理异常:", e);
            return Response.fail("微信公众平台自定义菜单/创建菜单成功异常");
        }
    }

    /**
     * 自定义菜单/删除菜单
     */
    @Override
    public Response deleteMenu() {
        log.info("【微信公众平台自定义菜单/删除菜单】");
        try {
            //构建请求参数
            Map<String, Object> paramMap = new HashMap<>(16);
            paramMap.put("access_token", ACCESS_TOKEN);
            log.info("【微信公众平台自定义菜单/删除菜单】请求参数:【{}】,请求地址:【{}】", JSON.toJSONString(paramMap), deleteMenuUrl);

            //执行请求,获取结果
            String result = HttpUtil.get(deleteMenuUrl, paramMap);
            JSONObject resultJsonObject = JSON.parseObject(result);
            log.info("【微信公众平台自定义菜单/删除菜单】响应结果:【{}】", resultJsonObject);

            if (0 != resultJsonObject.getIntValue("errcode")) {
                log.info("【微信公众平台自定义菜单/创建菜单】删除菜单失败");
                return Response.fail("自定义菜单/删除菜单失败");
            }

            log.info("【微信公众平台自定义菜单/删除菜单】删除菜单成功");
            return Response.success("删除菜单成功", resultJsonObject);
        } catch (Exception e) {
            log.info("【微信公众平台自定义菜单/删除菜单】处理异常:", e);
            return Response.fail("微信公众平台自定义菜单/删除菜单异常");
        }
    }

    /**
     * 构建测试菜单
     *
     * @param
     * @return com.zrj.wechat.entity.Menu
     */
    public static Menu buildMenuResp() {
        Menu menu = new Menu();
        BasicButton[] basicButtons = new BasicButton[0];

        ClickButton clickButton = new ClickButton();
        clickButton.setType("click");
        clickButton.setName("今日歌曲");
        clickButton.setKey("V1001_TODAY_MUSIC");
        clickButton.setSub_button(basicButtons);

        ClickButton clickButton1 = new ClickButton();
        clickButton1.setType("click");
        clickButton1.setName("歌手简介");
        clickButton1.setKey("V1001_TODAY_SINGER");
        clickButton1.setSub_button(basicButtons);

        ComplexButton complexButton = new ComplexButton();
        complexButton.setName("菜单");

        ViewButton viewButton = new ViewButton();
        viewButton.setType("view");
        viewButton.setName("搜索");
        viewButton.setUrl("https://www.baidu.com/");
        viewButton.setSub_button(basicButtons);

        ViewButton viewButton1 = new ViewButton();
        viewButton1.setType("view");
        viewButton1.setName("视频");
        viewButton1.setUrl("https://v.qq.com/");
        viewButton1.setSub_button(basicButtons);

        ClickButton clickButton2 = new ClickButton();
        clickButton2.setType("click");
        clickButton2.setName("赞一下我们");
        clickButton2.setKey("V1001_GOOD");
        clickButton2.setSub_button(basicButtons);

        BasicButton[] complexSubButton = {viewButton, viewButton1, clickButton2};
        complexButton.setSub_button(complexSubButton);

        BasicButton[] subMenu = {clickButton, clickButton1, complexButton};

        menu.setButton(subMenu);
        return menu;
    }
}

WechatMpController

package com.zrj.wechat.controller;

import com.zrj.wechat.entity.Response;
import com.zrj.wechat.service.WechatMpService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;

/**
 * 微信公众平台
 *
 * @author zrj
 * @since 2021/10/23
 **/
@Slf4j
@RestController
@RequestMapping("/wechat/mp")
@Api(tags = "微信公众平台服务", description = "微信公众平台服务")
public class WechatMpController {
    @Resource
    private WechatMpService wechatMpService;

    /**
     * 微信公众平台服务测试服务
     */
    @GetMapping("/test")
    @ApiOperation(value = "微信公众平台服务测试服务", notes = "测试方法的备注说明", httpMethod = "GET")
    public Response test() {
        log.info("【微信公众平台服务测试成功】");
        return Response.success("微信公众平台服务测试成功", null);
    }

    /**
     * 微信公众平台服务器地址URL服务
     * 公众号-基本配置-服务器配置-服务器地址(URL):http://zrj.free.idcfengye.com/wechat/token/security
     * https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html
     * 调用接口的IP地址不在白名单中,请在接口IP白名单中进行设置。
     * <p>
     * 测试号地址配置
     * 公众号-开发工具-公众平台测试账号-测试号管理-接口配置信息(URL):http://zrj.free.idcfengye.com/wechat/token/event
     * https://mp.weixin.qq.com/debug/cgi-bin/sandboxinfo?action=showinfo&t=sandbox/index
     *
     * @param signature    微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数
     * @param timestamp    时间戳
     * @param nonce        随机数
     * @param echostr      随机字符串
     * @param encryptType  encrypt_type(加密类型,为 aes)
     * @param msgSignature msg_signature(消息体签名,用于验证消息体的正确性)
     * @param openid       openid类似于公众号关注用户id
     * @param postData     消息体
     * @return java.lang.String
     */
    @RequestMapping(value = "/event")
    @ApiOperation(value = "微信公众平台授权事件接收URL验证票据")
    public String event(@RequestParam("signature") String signature,
                        @RequestParam("timestamp") String timestamp,
                        @RequestParam("nonce") String nonce,
                        @RequestParam(name = "echostr", required = false) String echostr,
                        @RequestParam(name = "encrypt_type", required = false) String encryptType,
                        @RequestParam(name = "msg_signature", required = false) String msgSignature,
                        @RequestParam(name = "openid", required = false) String openid,
                        @RequestBody(required = false) String postData) {

        return wechatMpService.event(signature, timestamp, nonce, echostr, encryptType, msgSignature, openid, postData);
    }

    /**
     * 微信公众平台获取AccessToken
     * access_token是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_token。
     * 开发者需要进行妥善保存。access_token的存储至少要保留512个字符空间。access_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的access_token失效。
     * https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html
     * 调用接口的IP地址不在白名单中,请在接口IP白名单中进行设置。
     */
    @GetMapping("/token")
    @ApiOperation(value = "微信公众平台获取AccessToken")
    public Response getAccessToken() {
        return wechatMpService.getAccessToken();
    }

    /**
     * 获取公众号的自动回复规则
     */
    @GetMapping("/getCurrentAutoreplyInfo")
    @ApiOperation(value = "获取公众号的自动回复规则")
    public Response getCurrentAutoreplyInfo() {
        return wechatMpService.getCurrentAutoreplyInfo();
    }

    /**
     * 获取微信服务器IP地址
     */
    @GetMapping("/getApiDomainIp")
    @ApiOperation(value = "获取微信服务器IP地址")
    public Response getApiDomainIp() {
        return wechatMpService.getApiDomainIp();
    }

    /**
     * 网络检测
     */
    @PostMapping("/check")
    @ApiOperation(value = "网络检测")
    public Response check() {
        return wechatMpService.check();
    }

    /**
     * 发布能力/获取成功发布列表
     */
    @GetMapping("/batchGetFreePublish")
    @ApiOperation(value = "发布能力/获取成功发布列表")
    public Response batchGetFreePublish() {
        return wechatMpService.batchGetFreePublish();
    }

    /**
     * 自定义菜单/获取自定义菜单配置
     */
    @GetMapping("/getMenu")
    @ApiOperation(value = "自定义菜单/获取自定义菜单", httpMethod = "GET")
    public Response<Object> getMenu() {
        return wechatMpService.getMenu();
    }

    /**
     * 自定义菜单/获取当前自定义菜单
     */
    @GetMapping("/getCurrentMenu")
    @ApiOperation(value = "自定义菜单/获取当前自定义菜单", httpMethod = "GET")
    public Response<Object> getCurrentMenu() {
        return wechatMpService.getCurrentMenu();
    }

    /**
     * 自定义菜单/创建菜单
     */
    @PostMapping("/createMenu")
    @ApiOperation(value = "自定义菜单/创建菜单")
    public Response<String> createMenu() {
        return wechatMpService.createMenu();
    }

    /**
     * 自定义菜单/删除菜单
     */
    @GetMapping("/deleteMenu")
    @ApiOperation(value = "自定义菜单/删除菜单")
    public Response<Object> deleteMenu() {
        return wechatMpService.deleteMenu();
    }
}

4.测试验证

测试网页:https://mp.weixin.qq.com/debug/cgi-bin/apiinfo
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值