Detailed explanation of Java docking with wechat public platform

Keywords: architecture

1. Overview of public platform

1.1 overview of public platform

The official account of official account is the platform for operators to provide information and services to WeChat users through the official account. The platform of public platform development is the foundation of providing services. After creating public numbers and obtaining interface permissions on the public platform website, developers can connect the backend interface to the WeChat public number. The WeChat public platform is the basis for providing services.

Official documents:
https://developers.weixin.qq.com/doc/offiaccount/Getting_Started/Overview.html

1.2 Introduction Guide

In order to lower the threshold and make up for deficiencies, we have prepared the Developer Guide to explain the basic and common functions of wechat open platform, which is designed to help you get started with the developer model of wechat open platform.

For developers who are familiar with the use of interfaces or have some public platform development experience, please skip this article directly. This article will not bring you great coding skills or in-depth explanation of interfaces. For queries about existing interfaces, you can visit the official account community exchange, contact Tencent customer service or use WeChat feedback.

Compared with the open platform, docking to the public platform is relatively simple. The whole process only needs to configure the development configuration and obtain the token through AppID and AppSecret. All subsequent interfaces are called through the token.

Developer guidelines:
https://developers.weixin.qq.com/doc/offiaccount/Getting_Started/Getting_Started_Guide.html

2. Docking process

2.1 access overview

To access wechat public platform development, developers need to follow the following steps:
1. Fill in server configuration
2. Verify the validity of the server address
3. Implement business logic according to interface documents

be careful:
1. the premise of completing the server configuration is to register the official account and have the relevant authority to register official account types.
2. The configuration server also needs to be able to connect to the external network callback, which needs to penetrate the internal network in advance( Sunny ngrok tutorial ), or ECs.

Different types of official account are:
https://developers.weixin.qq.com/doc/offiaccount/Getting_Started/Explanation_of_interface_privileges.html

Global return code Description:
https://developers.weixin.qq.com/doc/offiaccount/Getting_Started/Global_Return_Code.html

2.2 fill in server configuration

After logging into the official website of wechat public platform, on the development basic settings page of the official website of the public platform, check the protocol to become a developer, click the "modify configuration" button, and fill in the server address (URL), Token and encoding aeskey, where the URL is the interface URL used by the developer to receive wechat messages and events. The Token can be filled in arbitrarily by the developer for signature generation (the Token will be compared with the Token contained in the interface URL to verify the security). EncodingAESKey is filled in manually or randomly generated by the developer and will be used as the encryption and decryption key of the message body.

At the same time, developers can choose message encryption and decryption modes: plaintext mode, compatibility mode and security mode. The mode selection and server configuration will take effect immediately after submission. Please fill in and select carefully. The default state of encryption and decryption mode is plaintext mode. To select compatibility mode and security mode, relevant encryption and decryption codes need to be configured in advance, For details, please refer to the document of message body signature and encryption and decryption .

Fill in the server configuration description:
https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html

The configuration information is as follows

2.3 interface domain name Description

Public platform interface domain name Description
Developers can choose the best access domain name (lower delay and higher stability) according to their own server deployment. In addition, other access domain names can be used for disaster recovery. In case of network link failure, alternative domain names can be considered for access. Please use the domain name for API interface requests, and do not use IP as access. If it is necessary to open the network policy, the developer can obtain the latest IP information regularly from the IP address of the wechat server.

General domain name (api.weixin.qq.com), which will be used to access the nearest access point designated by the official;
General remote disaster recovery domain name (api2.weixin.qq.com). When the above domain name is not accessible, you can access this domain name instead;
Shanghai domain name (sh.api.weixin.qq.com), which will be used to access the access point in Shanghai;
Shenzhen domain name (sz.api.weixin.qq.com), which will be used to access the access point in Shenzhen;
Hong Kong Domain Name (hk.api.weixin.qq.com), which will be used to access the access point in Hong Kong.

Description of public platform interface domain name:
https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Interface_field_description.html

2.4 obtaining Access token

access_token is the global only interface calling credential for official account. When calling the interfaces, the official account needs to use access_. token. Developers need to keep it properly. access_ At least 512 character space should be reserved for token storage. access_ The validity period of the token is currently 2 hours. It needs to be refreshed regularly. Repeated acquisition will result in the access obtained last time_ The token is invalid.

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

3. Project realization

3.1 create project

When creating a springboot project, there are still many places where the code can be optimized, such as messages can be used as strategies, enumeration types, constants and authorization information in the code can be cached, etc. because only tests are done here, they can be optimized by themselves in combination with tests.
In order to facilitate the test, these authorization parameters are put into the collection memory, so they are lost every time you restart. In the later stage, it is necessary to optimize the redis cache persistence. The validity period of the verification ticket is 12 hours. Therefore, in order to test, you can write them dead, and you can get others from memory.

In addition, there are many open-source wechat development frameworks that can be used for reference. Here, we rely on the introduction of wechat development framework Weixin java tools binarywang just for learning, research and use.
weixin-Java-tools: https://gitee.com/itwu/weixin-java-tools#https://mp.weixin.qq.com/s/nIk_xOf6dxkhKfqq830Cuw

3.2 project dependency

<?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>
        <!--Database connection-->
        <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>
        <!--Common tools-->
        <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 rely on-->
        <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>
        <!--Wechat encryption and decryption-->
        <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>
        <!--Field verification-->
        <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>
        <!-- Wechat development framework 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 project configuration

Not used here for the time being

3.4 encryption and decryption tools

Message encryption and decryption Description:
https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/api/Before_Develop/Message_encryption_and_decryption.html

Download address of encryption and decryption tool: https://res.wx.qq.com/op_res/-serEQ6xSDVIjfoOHcX78T1JAYX-pM_fghzfiNYoD8uHVd3fOeC0PC_pvlg4-kmP

1. After downloading and decompressing, select the corresponding tool class according to the corresponding language, directly type it into a jar package and put it into the private server reference or the project reference.
2. The rack package commons-codec-1.9 (or other versions such as commons-codec-1.8) needs to be imported
3. official account official account should be noted that the encryption and decryption parameters are different from the public number, because the xml returned is different. If the official account is used to add the secret numbers, there will be null pointer. It needs to be adjusted according to the result of return. After the public number is encrypted, xml is ToUser and Encrypt tags. The return of open platform is AppId and Encrypt tags, Encrypt is encrypted content.

/**
 * For org.apache.commons.codec.binary.Base64,
 * The rack package commons-codec-1.9 (or other versions such as commons-codec-1.8) needs to be imported
 * Official download address: http://commons.apache.org/proper/commons-codec/download_codec.cgi
 */

/**
 * Provide the encryption and decryption interface (UTF8 encoded string) for receiving and pushing messages to the public platform
 * <ol>
 * 	<li>The third party replies the encrypted message to the public platform</li>
 * 	<li>The third party receives the message sent by the public platform, verifies the security of the message, and decrypts the message</ li>
 * </ol>
 * Description: exception java.security.invalidkeyexception: solution to illegal key size
 * <ol>
 * 	<li>Download JCE unrestricted permission policy file (JDK7) on the official website at:
 *      http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html</li>
 * 	<li>After downloading and decompressing, you can see local_policy.jar and US_export_policy.jar and readme.txt</li>
 * 	<li>If JRE is installed, put two jar files in% JRE_ Overwrite the original file in the home% \ lib \ security directory</li>
 * 	<li>If JDK is installed, put two jar files into% JDK_ Overwrite the original file in the home% \ JRE \ lib \ security directory</li>
 * </ol>
 */

3.5 code implementation

WechatMpService

package com.zrj.wechat.service;

import com.zrj.wechat.entity.Response;

/**
 * Wechat public platform interface
 *
 * @author zrj
 * @since 2021/9/23
 **/
public interface WechatMpService {
    /**
     * Wechat public platform service test service
     *
     * @param signature Wechat encryption signature combines the token parameter filled in by the developer with the timestamp parameter and nonce parameter in the request.
     * @param timestamp time stamp
     * @param nonce     random number
     * @param echostr   Random string
     * @return Random string
     */
    String security(String signature, String timestamp, String nonce, String echostr);

    /**
     * Wechat public platform message and event push and receive service
     *
     * @param signature Wechat encryption signature combines the token parameter filled in by the developer with the timestamp parameter and nonce parameter in the request
     * @param timestamp time stamp
     * @param nonce     random number
     * @param echostr   Random string
     * @param postData  Message body
     * @return If you get, you only need to return SUCCESS
     */
    String event(String signature, String timestamp, String nonce, String echostr, String postData);

    /**
     * Obtain AccessToken on wechat public platform
     */
    Response getAccessToken();

    /**
     * Automatic recovery rules for official account numbers
     */
    Response getCurrentAutoreplyInfo();

    /**
     * Get the IP address of wechat server
     */
    Response getApiDomainIp();

    /**
     * Network detection
     */
    Response check();

    /**
     * Get list of successful publications
     */
    Response batchGetFreePublish();

    /**
     * Custom menu / get custom menu configuration (get default menu and all personalized menu information)
     */
    Response<Object> getMenu();

    /**
     * Custom menu / get the current custom menu (website function publishing menu and menu set through API call)
     */
    Response<Object> getCurrentMenu();

    /**
     * Customize menu / create menu
     */
    Response createMenu();

    /**
     * Customize menu / delete menu
     */
    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.constants.WechatConstants;
import com.zrj.wechat.entity.Response;
import com.zrj.wechat.enums.WechatEventEnum;
import com.zrj.wechat.service.WechatMpService;
import com.zrj.wechat.utils.Demo;
import com.zrj.wechat.utils.SignaUtil;
import com.zrj.wechat.utils.WechatUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

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

/**
 * Wechat public platform interface implementation class
 *
 * @author zrj
 * @since 2021/9/27
 **/
@Slf4j
@Service("WechatMpServiceImpl")
public class WechatMpServiceImpl implements WechatMpService {

    //Get access_ Fill in client with token_ credential
    public static final String GRANT_TYPE = "client_credential";
    //Third party user unique voucher
    public static final String APP_ID = "wx0e5c56c1257a4503";
    //Third party user's unique credential key, i.e. appSecret
    public static final String APPSECRET = "eb136535b688f2056d1999a71d2e01d1";
    //Get access_token interface address
    public static final String GET_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token";
    //Token access_token, which can be put into Redis cache for easy access and regular refresh
    public static final String ACCESS_TOKEN = "50_MokEH1zPUczovVi9mArB_tfzD0ttJbjpQesziWsrT5Y0zrIkyoAY0rSRQXDzmGmENJpwBfh9g5fFmSsBcfnURO3PHnybpgz64thgq5pGkGXrQ1uw49VkqAuswtTmGQk5j1guxCghIN9O0mYqXUSbADADAY";

    //The official account token (Token) can be optimized to the configuration center, which is customized and consistent with the public platform configuration.
    public static final String MP_TOKEN = "zrj";
    //Official account developer ID(AppID)
    public static final String PMP_APP_ID = "wx0e5c56c1257a4503";
    //Official account message encryption key EncodingAESKey
    public static final String MP_ENCODING_AES_KEY = "tVNZZP2WuEJwZDF1TpIZB0vcIGjjfAcnZxUAYHvKbl6";

    //Automatic recovery rules for official account numbers
    public static final String GET_CURRENT_AUTOREPLY_INFO = "https://api.weixin.qq.com/cgi-bin/get_current_autoreply_info";
    //Get the IP address of wechat server
    public static final String GET_API_DOMAIN_IP = "https://api.weixin.qq.com/cgi-bin/get_api_domain_ip";
    //Network detection
    public static final String CHECK = "https://api.weixin.qq.com/cgi-bin/callback/check?access_token=ACCESS_TOKEN";
    //Publishing capability: get the list of successful releases
    public static final String BATCH_GET_FREE_PUBLISH = "https://api.weixin.qq.com/cgi-bin/freepublish/batchget?access_token=ACCESS_TOKEN";

    //GET the user-defined menu configuration (GET the default menu and all personalized menu information). The user-defined menu query interface can only query the menu configuration set using the API
    private static final String getMenuUrl = "https://api.weixin.qq.com/cgi-bin/menu/get";
    //Currently used custom menus (website function publishing menu and menu set through API call) GET
    private static final String getCurrentMenuUrl = "https://api.weixin.qq.com/cgi-bin/get_current_selfmenu_info";
    //Create menu POST
    private static final String createMenuUrl = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN";
    //Delete menu GET
    private static final String deleteMenuUrl = "https://api.weixin.qq.com/cgi-bin/menu/delete";

    /**
     * Wechat public platform service test service
     *
     * @param signature Wechat encryption signature combines the token parameter filled in by the developer with the timestamp parameter and nonce parameter in the request.
     * @param timestamp time stamp
     * @param nonce     random number
     * @param echostr   Random string
     * @return Random string
     */
    @Override
    public String security(String signature, String timestamp, String nonce, String echostr) {
        log.info("[Wechat public platform server address URL [service] request parameters: signature: [{}],timestamp: [{}],nonce: [{}],echostr: [{}]", signature, timestamp, nonce, echostr);

        if (StringUtils.isAnyBlank(signature, timestamp, nonce, echostr)) {
            log.info("[Wechat public platform server address URL Service] request parameter is null!");
            throw new IllegalArgumentException("The request parameter is illegal, please verify!");
        }

        if (SignaUtil.checkSignature(signature, timestamp, nonce)) {
            log.info("[Wechat public platform server address URL Service] wechat authentication illegal request!");
        }

        log.info("[Wechat public platform server address URL Service success]");
        return echostr;
    }

    /**
     * Wechat public platform message and event push and receive service
     * If the wechat server fails to receive a response within five seconds, it will disconnect and re initiate the request. It will retry three times in total
     * If developers want to enhance security, they can open message encryption at the developer center, so that users will continue to encrypt messages and send official account messages to the official account.
     * If the server cannot guarantee to process and reply within five seconds, it must make the following reply, so that the wechat server will not process this and will not initiate retry (in this case, you can use the customer service message interface for asynchronous reply), otherwise, a serious error message will appear. See the following description for details:
     * 1,Directly reply to success (recommended method) 2. Directly reply to an empty string (refers to an empty string with byte length of 0, rather than the content field in the XML structure is empty)
     * WeChat will send a system to the user in the official account session, "if the official account is temporarily unable to provide services, please try again later":
     * 1,The developer did not reply to any content within 5 seconds. 2. The developer replied to abnormal data, such as JSON data
     * In addition, please note that when replying to multimedia messages such as pictures (gif moving pictures are not supported), you need to upload temporary materials to the wechat server through the material management interface in advance. You can use temporary materials in material management or permanent materials.
     * Note: 3 seconds of trouble in the test, "the official account is temporarily unable to provide services, please try again later", directly return to "success", no exception.
     *
     * @param signature Wechat encryption signature combines the token parameter filled in by the developer with the timestamp parameter and nonce parameter in the request
     * @param timestamp time stamp
     * @param nonce     random number
     * @param echostr   Random string
     * @param postData  Message body
     * @return If you get, you only need to return SUCCESS
     */
    @Override
    public String event(String signature, String timestamp, String nonce, String echostr, String postData) {
        log.info("[Wechat public platform message event receiving service] request parameters: signature: [{}],timestamp: [{}],nonce: [{}],echostr: [{}],postData: [{}]", timestamp, nonce, echostr, signature, postData);

        //Configure url validation
        if (StrUtil.isEmpty(postData)) {
            log.info("[Wechat public platform message event receiving service] if the message body is empty, it will directly return the successful verification and the random number echostr: {}", echostr);
            return echostr;
        }

        // The 3 seconds of the test failed to indicate that the official account is temporarily unable to provide services. Please try again later, and return directly to "success", no exception.
        //if (true) {
        //    return "success";
        //}

        //Receive event messages
        try {
            //This class is the decryption class provided by the wechat official website. It requires message verification Token, message encryption Key and service platform appid
            WXBizMsgCrypt pc = new WXBizMsgCrypt(WechatConstants.PLATFORM_COMPONENT_TOKEN, WechatConstants.PLATFORM_AES_KEY, WechatConstants.PLATFORM_APP_ID);
            //Encryption mode: decryption required
            //String xml = pc.decryptMsg(signature, timestamp, nonce, postData);
            //Plaintext mode
            String xml = postData;
            // Convert xml to map
            Map<String, String> result = WechatUtils.xmlToMap(xml);
            log.info("[Wechat public platform message event receiving service] receives text messages:{}", result);

            //Build the message body to return and reply to the text message
            Map<String, Object> respMsgMap = new HashMap<>(16);
            if (WechatEventEnum.MSGTYPE_TEXT.getCode().equals(result.get("MsgType"))) {
                log.info("[Wechat public platform message event receiving service] text message type");
                respMsgMap.put("ToUserName", "<![CDATA[" + result.get("ToUserName") + "]]>");
                respMsgMap.put("FromUserName", "<![CDATA[" + result.get("FromUserName") + "]]>");
                respMsgMap.put("CreateTime", result.get("CreateTime"));
                respMsgMap.put("MsgType", "<![CDATA[" + result.get("MsgType") + "]]>");
                respMsgMap.put("Content", "<![CDATA[Thank you for looking so good and paying attention to me[rose][rose][rose]]]>");
            } else {
                log.info("[Wechat public platform message event receiving service] other types of messages will not be processed temporarily");
                respMsgMap.putAll(result);
            }

            String respMsgXml = WechatUtils.mapToXml(respMsgMap);
            log.info("[Wechat public platform message event receiving service succeeded] message reply:{}", respMsgXml);
            return respMsgXml;
        } catch (Exception e) {
            log.info("[Wechat public platform message event receiving service] event receiving exception:", e);
            return WechatConstants.TICKET_FAIL;
        }
    }

    /**
     * Obtain AccessToken on wechat public platform
     * ?grant_type=client_credential&appid=APPID&secret=APPSECRET
     */
    @Override
    public Response getAccessToken() {
        log.info("[Wechat public platform access AccessToken Services]");

        try {
            //Build request parameters
            Map<String, Object> paramMap = new HashMap<>(16);
            paramMap.put("grant_type", GRANT_TYPE);
            paramMap.put("appid", APP_ID);
            paramMap.put("secret", APPSECRET);
            log.info("[Wechat public platform access AccessToken [interface] request parameters:[{}],Request address:[{}]", JSON.toJSONString(paramMap), GET_ACCESS_TOKEN_URL);

            //Execute the request and get the result
            String result = HttpUtil.get(GET_ACCESS_TOKEN_URL, paramMap);
            JSONObject resultJsonObject = JSON.parseObject(result);

            log.info("[Wechat public platform access AccessToken [interface] response result:[{}]", resultJsonObject);
            return Response.success("", resultJsonObject);
        } catch (Exception e) {
            log.info("[Wechat public platform message event receiving service] event receiving exception:", e);
            return Response.fail("Wechat public platform message event receiving service exception");
        }
    }

    /**
     * Automatic recovery rules for official account numbers
     * ?access_token=ACCESS_TOKEN
     */
    @Override
    public Response getCurrentAutoreplyInfo() {
        log.info("[Get the automatic return rule for official account number.");

        try {
            //Build request parameters
            Map<String, Object> paramMap = new HashMap<>(16);
            paramMap.put("access_token", ACCESS_TOKEN);
            log.info("[Get the official account's automatic return rule: request parameter:{}],Request address:[{}]", JSON.toJSONString(paramMap), GET_ACCESS_TOKEN_URL);

            //Execute the request and get the result
            String result = HttpUtil.get(GET_CURRENT_AUTOREPLY_INFO, paramMap);
            JSONObject resultJsonObject = JSON.parseObject(result);

            log.info("[Get the automatic reply rule of official account.{}]", resultJsonObject);
            return Response.success("", resultJsonObject);
        } catch (Exception e) {
            log.info("[Get the automatic reply rule of official account] handle exceptions:", e);
            return Response.fail("Get the automatic rule of the official account exception.");
        }
    }

    /**
     * Get the IP address of wechat server
     * https://api.weixin.qq.com/cgi-bin/get_api_domain_ip?access_token=ACCESS_TOKEN
     */
    @Override
    public Response getApiDomainIp() {
        log.info("[Get wechat server IP Address]");

        try {
            //Build request parameters
            Map<String, Object> paramMap = new HashMap<>(16);
            paramMap.put("access_token", ACCESS_TOKEN);
            log.info("[Get wechat server IP [address] request parameters:[{}],Request address:[{}]", JSON.toJSONString(paramMap), GET_API_DOMAIN_IP);

            //Execute the request and get the result
            String result = HttpUtil.get(GET_API_DOMAIN_IP, paramMap);
            JSONObject resultJsonObject = JSON.parseObject(result);

            log.info("[Get wechat server IP [address] response result:[{}]", resultJsonObject);
            return Response.success("", resultJsonObject);
        } catch (Exception e) {
            log.info("[Get wechat server IP Address] exception handling:", e);
            return Response.fail("Get wechat server IP Address exception");
        }
    }

    /**
     * Network detection
     */
    @Override
    public Response check() {
        log.info("[[network detection service]");

        try {
            //Build request address access_token  	 Yes 	 Calling interface credentials
            String getCheckUrl = CHECK.replaceAll("ACCESS_TOKEN", ACCESS_TOKEN);

            //Build request parameters
            Map<String, Object> paramMap = new HashMap<>(16);
            //action is 	 The allowable values of the detection actions performed: dns (domain name resolution), ping (ping detection), all (both dns and ping)
            paramMap.put("action", "all");
            //check_operator is 	 The specified platform detects from an operator. The allowed values are CHINANET (Telecom export), UNICOM (UNICOM export), CAP (Tencent self built export), DEFAULT (select the operator according to ip)
            paramMap.put("check_operator", "DEFAULT");
            String jsonString = JSON.toJSONString(paramMap);
            log.info("[[network detection] request parameters:[{}],Request address:[{}]", jsonString, getCheckUrl);

            //Execute the request and get the result
            String result = HttpUtil.post(getCheckUrl, jsonString);
            JSONObject resultJsonObject = JSON.parseObject(result);

            log.info("[[network detection] response result:[{}]", resultJsonObject);
            return Response.success("", resultJsonObject);
        } catch (Exception e) {
            log.info("[Network detection] processing exception:", e);
            return Response.fail("Network detection exception");
        }
    }

    /**
     * Get list of successful publications
     */
    @Override
    public Response batchGetFreePublish() {
        log.info("[Release capability/Get list of successful releases]");

        try {
            //Build request address access_token  	 Yes 	 Calling interface credentials
            String getFreePublishUrl = BATCH_GET_FREE_PUBLISH.replaceAll("ACCESS_TOKEN", ACCESS_TOKEN);

            //Build request parameters
            Map<String, Object> paramMap = new HashMap<>(16);
            //Offset is 	 Return from the offset position of all materials, and 0 means return from the first material
            paramMap.put("offset", 0);
            //count is 	 Returns the number of materials, with a value between 1 and 20
            paramMap.put("count", 20);
            //no_content no 	 1 means no content field is returned, 0 means normal return, and the default is 0
            paramMap.put("no_content", 0);
            String jsonString = JSON.toJSONString(paramMap);
            log.info("[Get request parameters of [successfully published list]:[{}],Request address:[{}]", jsonString, getFreePublishUrl);

            //Execute the request and get the result
            String result = HttpUtil.post(getFreePublishUrl, jsonString);
            JSONObject resultJsonObject = JSON.parseObject(result);

            log.info("[Get the response result of [successfully published list]:[{}]", resultJsonObject);
            return Response.success("", resultJsonObject);
        } catch (Exception e) {
            log.info("[Get successfully published list] exception handling:", e);
            return Response.fail("Exception in getting successful publishing list");
        }
    }

    /**
     * Customize menu / get custom menu configuration
     * There is no personalized menu in phase I by default
     * [Get the response result of custom menu configuration: [{"errcode":48001,"errmsg":"api unauthorized rid: 6174118f-158c1dc7-5cf547b1"}]
     */
    @Override
    public Response<Object> getMenu() {
        log.info("[Get custom menu configuration]");
        try {
            //Build request parameters
            Map<String, Object> paramMap = new HashMap<>(16);
            paramMap.put("access_token", ACCESS_TOKEN);
            log.info("[To obtain the request parameters for custom menu configuration:[{}],Request address:[{}]", JSON.toJSONString(paramMap), getMenuUrl);

            //Execute the request and get the result
            String result = HttpUtil.get(getMenuUrl, paramMap);
            JSONObject resultJsonObject = JSON.parseObject(result);

            log.info("[Get the response result of custom menu configuration:[{}]", resultJsonObject);
            return Response.success("", resultJsonObject);
        } catch (Exception e) {
            log.info("[Get custom menu configuration] exception handling:", e);
            return Response.fail("Get custom menu configuration exception");
        }
    }

    /**
     * Custom menu / get the current custom menu (website function publishing menu and menu set through API call)
     */
    @Override
    public Response<Object> getCurrentMenu() {
        log.info("[Get current custom menu]");
        try {
            //Build request parameters
            Map<String, Object> paramMap = new HashMap<>(16);
            paramMap.put("access_token", ACCESS_TOKEN);
            log.info("[Get the request parameters of current custom menu:[{}],Request address:[{}]", JSON.toJSONString(paramMap), getCurrentMenuUrl);

            //Execute the request and get the result
            String result = HttpUtil.get(getCurrentMenuUrl, paramMap);
            JSONObject resultJsonObject = JSON.parseObject(result);

            log.info("[Get the response result of current custom menu:[{}]", resultJsonObject);
            return Response.success("", resultJsonObject);
        } catch (Exception e) {
            log.info("[Get current custom menu] exception handling:", e);
            return Response.fail("Get current custom menu exception");
        }
    }

    /**
     * Customize menu / create menu
     * Currently, it supports click, view and mixing
     */
    @Override
    public Response createMenu() {
        log.info("[Customize menu/Create menu]");
        try {
            //Build request address access_token  	 Yes 	 Calling interface credentials
            String getCreateMenuUrl = createMenuUrl.replaceAll("ACCESS_TOKEN", ACCESS_TOKEN);

            //Build request object
            String jsonString = JSON.toJSONString(Demo.buildMenuResp());
            log.info("[Customize menu/Create menu] request parameters:[{}],Request address:[{}]", jsonString, getCreateMenuUrl);

            //Execute the request and get the result
            String result = HttpUtil.post(getCreateMenuUrl, jsonString);
            JSONObject resultJsonObject = JSON.parseObject(result);
            log.info("[Customize menu/Create menu] response result:[{}]", resultJsonObject);

            if (0 != resultJsonObject.getIntValue("errcode")) {
                return Response.fail("Customize menu/Failed to create menu");
            }

            log.info("[Customize menu/[menu created successfully]");
            return Response.success("Customize menu/Menu created successfully", resultJsonObject);
        } catch (Exception e) {
            log.info("[Customize menu/Create menu] exception handling:", e);
            return Response.fail("Customize menu/Menu creation succeeded exception");
        }
    }

    /**
     * Customize menu / delete menu
     */
    @Override
    public Response deleteMenu() {
        log.info("[Customize menu/Delete menu]");
        try {
            //Build request parameters
            Map<String, Object> paramMap = new HashMap<>(16);
            paramMap.put("access_token", ACCESS_TOKEN);
            log.info("[Customize menu/Delete menu] request parameters:[{}],Request address:[{}]", JSON.toJSONString(paramMap), deleteMenuUrl);

            //Execute the request and get the result
            String result = HttpUtil.get(deleteMenuUrl, paramMap);
            JSONObject resultJsonObject = JSON.parseObject(result);

            log.info("[Customize menu/Delete menu] response result:[{}]", resultJsonObject);
            return Response.success("", resultJsonObject);
        } catch (Exception e) {
            log.info("[Customize menu/Delete menu] exception handling:", e);
            return Response.fail("Customize menu/Delete menu exception");
        }
    }

}

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;

/**
 * Wechat public platform
 *
 * @author zrj
 * @since 2021/10/23
 **/
@Slf4j
@RestController
@RequestMapping("/wechat/mp")
@Api(tags = "Wechat public platform service", description = "Wechat public platform service")
public class WechatMpController {
    @Resource
    private WechatMpService wechatMpService;

    /**
     * Wechat public platform service test service
     */
    @GetMapping("/test")
    @ApiOperation(value = "Wechat public platform service test service", notes = "Remarks on test method", httpMethod = "GET")
    public Response test() {
        log.info("[Wechat public platform service test succeeded]");
        return Response.success("Wechat public platform service test succeeded", null);
    }

    /**
     * Wechat public platform server address URL service
     * Official account - basic configuration - server configuration - server address (URL): http://zrj.free.idcfengye.com/wechat/token/security
     * https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html
     * The IP address of the calling interface is not in the white list, please set it in the interface IP white list.
     *
     * @param signature Wechat encryption signature combines the token parameter filled in by the developer with the timestamp parameter and nonce parameter in the request
     * @param timestamp time stamp
     * @param nonce     random number
     * @param echostr   Random string
     * @return Random string
     */
    @GetMapping(value = "/security", produces = "text/plain;charset=utf-8")
    @ApiOperation(value = "Wechat public platform server address URL service", notes = "Wechat authentication description", httpMethod = "GET")
    public String security(@RequestParam(name = "signature", required = false) String signature,
                           @RequestParam(name = "timestamp", required = false) String timestamp,
                           @RequestParam(name = "nonce", required = false) String nonce,
                           @RequestParam(name = "echostr", required = false) String echostr) {

        return wechatMpService.security(signature, timestamp, nonce, echostr);
    }

    /**
     * Wechat public platform message and event push and receive service
     * Official account development tool public platform test account test number management interface configuration information (URL): http://zrj.free.idcfengye.com/wechat/token/event
     * https://mp.weixin.qq.com/debug/cgi-bin/sandboxinfo?action=showinfo&t=sandbox/index
     * 1.Authorization event receive URL
     * 2.Verification note
     *
     * @param signature Wechat encryption signature combines the token parameter filled in by the developer with the timestamp parameter and nonce parameter in the request
     * @param timestamp time stamp
     * @param nonce     random number
     * @param echostr   Random string
     * @param postData  Message body
     * @return java.lang.String
     */
    @PostMapping(value = "/event", produces = "application/xml; charset=UTF-8")
    @ApiOperation(value = "Wechat public platform authorization event reception URL Verification note")
    public String event(@RequestParam("signature") String signature,
                        @RequestParam("timestamp") String timestamp,
                        @RequestParam("nonce") String nonce,
                        @RequestParam(required = false) String echostr,
                        @RequestBody(required = false) String postData) {

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

    /**
     * Obtain AccessToken on wechat public platform
     * access_token The official account is the only interface call credential for public numbers. When calling the interfaces, the official account needs to use access_. token. 
     * Developers need to keep it properly. access_ At least 512 character space should be reserved for token storage. access_ The validity period of the token is currently 2 hours. It needs to be refreshed regularly. Repeated acquisition will result in the access obtained last time_ The token is invalid.
     * https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html
     * The IP address of the calling interface is not in the white list, please set it in the interface IP white list.
     */
    @GetMapping("/token")
    @ApiOperation(value = "Wechat public platform access AccessToken")
    public Response getAccessToken() {
        return wechatMpService.getAccessToken();
    }

    /**
     * Automatic recovery rules for official account numbers
     */
    @GetMapping("/getCurrentAutoreplyInfo")
    @ApiOperation(value = "Automatic recovery rules for official account numbers")
    public Response getCurrentAutoreplyInfo() {
        return wechatMpService.getCurrentAutoreplyInfo();
    }

    /**
     * Get the IP address of wechat server
     */
    @GetMapping("/getApiDomainIp")
    @ApiOperation(value = "Get wechat server IP address")
    public Response getApiDomainIp() {
        return wechatMpService.getApiDomainIp();
    }

    /**
     * Network detection
     */
    @PostMapping("/check")
    @ApiOperation(value = "Network detection")
    public Response check() {
        return wechatMpService.check();
    }

    /**
     * Publishing capability / get list of successful releases
     */
    @GetMapping("/batchGetFreePublish")
    @ApiOperation(value = "Release capability/Get list of successful publications")
    public Response batchGetFreePublish() {
        return wechatMpService.batchGetFreePublish();
    }

    /**
     * Customize menu / get custom menu configuration
     */
    @GetMapping("/getMenu")
    @ApiOperation(value = "Customize menu/Get custom menu", httpMethod = "GET")
    public Response<Object> getMenu() {
        return wechatMpService.getMenu();
    }

    /**
     * Custom menu / get current custom menu
     */
    @GetMapping("/getCurrentMenu")
    @ApiOperation(value = "Customize menu/Get current custom menu", httpMethod = "GET")
    public Response<Object> getCurrentMenu() {
        return wechatMpService.getCurrentMenu();
    }

    /**
     * Customize menu / create menu
     */
    @PostMapping("/createMenu")
    @ApiOperation(value = "Customize menu/create menu")
    public Response<String> createMenu() {
        return wechatMpService.createMenu();
    }

    /**
     * Customize menu / delete menu
     */
    @GetMapping("/deleteMenu")
    @ApiOperation(value = "Customize menu/Delete menu")
    public Response<Object> deleteMenu() {
        return wechatMpService.deleteMenu();
    }
}

4. Test verification

Test page: https://mp.weixin.qq.com/debug/cgi-bin/apiinfo?t=index&type=%E8%87%AA%E5%AE%9A%E4%B9%89%E8%8F%9C%E5%8D%95&form=%E8%87%AA%E5%AE%9A%E4%B9%89%E8%8F%9C%E5%8D%95%E5%88%9B%E5%BB%BA%E6%8E%A5%E5%8F%A3%20/menu/creat

Posted by sashi34u on Thu, 28 Oct 2021 04:11:23 -0700