Public Number Development: Get User Messages and Reply Messages

Recently, I found it interesting to see the development documentation of WeChat Public Number. You can customize the development of some functions, such as making a slightly more complex response after someone has paid attention to the Public Number (simple replies are configurable in the Public Number background). For example, if a follower sends a Learning message, you can push some articles to him and send him a Weather message.You can reply to current weather conditions; you can also manage materials, users, etc.

Today, let's start with the simplest way to get the message from a follower and reply to him with the same message, supporting text messages, pictures, and voice.Then unlock the other positions.

First let's look at the end result:

Next, I will start with the following steps:

  1. Application Test Public Number

  2. Configure Server Address

  3. Verify the validity of the server address

  4. Receive message, reply message

Application Test Public Number

The first step is to apply for a test WeChat Public Number, Address:

https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login

Configure Server Address

On the Test Number page, there is an interface configuration information option, which is configured below:

There are two configurations: the server address (URL), Token, and the option EncodingAESKey on the official public number.

  1. URL: The interface URL that developers use to receive WeChat messages and events.

  2. Token can be optionally filled in and used to generate signatures (it compares to Token contained in the interface URL to verify security).

  3. Encoding AESKey is manually filled in or randomly generated by the developer and will be used as the message body encryption and decryption key.

When you enter this URL and Token click Save, you need to start in the background and verify that Token is passed before you can save, otherwise the save will fail, so start the background code first.

Verify Token

When you fill in the URL, Token and click Save, WeChat will send the signature, timestamp, nonce and echostr to the background by GET. We validate the Token according to these four parameters.

The verification steps are as follows:

  1. Dictionary ordering of three parameters, token, timestamp, and nonce.

  2. sha1 encryption is performed by concatenating three parameter strings into a single string.

  3. Obtain an encrypted string and compare it to a signature. If it is equal, echostr is returned, indicating that the configuration is successful, otherwise null is returned and the configuration fails.

The code is as follows

First inApplication.propertiesThe configuration file configures the project startup ports, Token, appId and appsecret, where Token is freely written and can be seen on the Test Public Number page as long as it is consistent with the page configuration, as follows:

server.port=80
wechat.interface.config.token=wechattoken
wechat.appid=wxfe39186d102xxxxx
wechat.appsecret=cac2d5d68f0270ea37bd6110b26xxxx

Then map these configuration information to the class properties:

@Component
public class WechatUtils {
    @Value("${wechat.interface.config.token}")
    private String token;
    @Value("${wechat.appid}")
    private String appid;
    @Value("${wechat.appsecret}")
    private String appsecret;

    public String getToken() return token; }
    public String getAppid() {return appid;}
    public String getAppsecret() {return appsecret;}
}

Then, there needs to be a way to receive the four parameters that WeChat passes in. The requested URL and the page configuration need to be the same, as follows:

@RestController
public class WechatController {
    /** Log*/
    private Logger logger = LoggerFactory.getLogger(getClass());
    /** Tool class*/
    @Autowired
    private WechatUtils wechatUtils;

    /**
     * Verification of WeChat Public Number Interface Configuration
     * @return
     */
    @RequestMapping(value = "/wechat", method = RequestMethod.GET)
    public String checkSignature(String signature, String timestamp, 
                                     String nonce, String echostr) {
        logger.info("signature = {}", signature);
        logger.info("timestamp = {}", timestamp);
        logger.info("nonce = {}", nonce);
        logger.info("echostr = {}", echostr);
        //Step 1: Natural Sorting
        String[] tmp = {wechatUtils.getToken(), timestamp, nonce};
        Arrays.sort(tmp);
        //Step 2: sha1 Encryption
        String sourceStr = StringUtils.join(tmp);
        String localSignature = DigestUtils.sha1Hex(sourceStr);
        //Step 3: Verify the signature
        if (signature.equals(localSignature)) {
            return echostr;
        }
        return null;
    }
}

It is important to note here that the request must be GET. POST is the way that WeChat sends messages or events based on this URL every time a user sends a message to a public number, or generates a custom menu, or generates a WeChat payment order.

Start the project when testing the public number configuration URL and Token:

You will find that the save failed, no messages were received in the background, and no logs were printed; this is because the project was started locally and the access address was 127.0.0.1. When you click on the save, it was sent by Tencent server. People's servers naturally can't access your local area, so we need an intranet penetration tool to map our local address toOn the Internet, so Tencent servers can send messages.

I'm using natapp, which runs as follows:

Http://pquigs.natappfree.ccThis Internet address maps to our local 127.0.0.1 address, which is configured on the page:

At this point, you can save successfully and print the log as follows:

Now that we have successfully configured the server, we can call the Public Number Interface to develop it.

access_token

What is access_The description on the token? Website is as follows:

access_token is the globally unique interface call credential for the public number, which calls each interface using access_token.Developers need to save properly.Access_Storage of tokens should be kept at least 512 characters.Access_The token is currently valid for 2 hours and needs to be refreshed periodically. Duplicate fetches result in access_from the last fetchToken is invalid.

Get access_Daily interface calls to token are limited, so access_is not retrieved every time the interface is calledToken, instead, cache it after it is acquired and refresh it after it has failed.

Official documents:https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html

Since the public number development interface is basically an XML-formatted report, we can use the open source tool weixin-java-mp to develop it simply.Pom.xmlFile additions depend on the following:

        <dependency>
            <groupId>me.chanjar</groupId>
            <artifactId>weixin-java-mp</artifactId>
            <version>1.3.3</version>
        </dependency>
        <dependency>
            <groupId>me.chanjar</groupId>
            <artifactId>weixin-java-common</artifactId>
            <version>1.3.3</version>
        </dependency>

There are two important classes in weixin-java-mp, one is the configuration-related WxMpInMemoryConfigStorage and the other is the invocation of WxMpServiceImpl related to the WeChat interface.

Get access_token

Now get access_token:

  1. The interface address is:

https Request Method: https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
  1. Parameter Description

parameter Is it necessary Explain
grant_type yes Get access_token fills in client_credential
appid yes Third party user unique credentials
secret yes Third-party user unique credential key, appsecret
  1. Returns a JSON-formatted report:

{"access_token":"ACCESS_TOKEN","expires_in":7200}

The code is as follows

Initialize WxMpInMemoryConfigStorage and WxMpServiceImpl in the tool class WechatUtils at the beginning of the article, then call the getAccessToken() method of WxMpServiceImpl to get access_token:

/**
 * WeChat Tool Class
 */
@Component
public class WechatUtils {
    @Value("${wechat.interface.config.token}")
    private String token;
    @Value("${wechat.appid}")
    private String appid;
    @Value("${wechat.appsecret}")
    private String appsecret;

    public String getToken() {return token;}
    public String getAppid() {return appid;}
    public String getAppsecret() {return appsecret;}

    /**
     * Call WeChat Interface
     */
    private WxMpService wxMpService;

    /**
     * Initialization
     */
    @PostConstruct
    private void init() {
        WxMpInMemoryConfigStorage wxMpConfigStorage = new WxMpInMemoryConfigStorage();
        wxMpConfigStorage.setAppId(appid);
        wxMpConfigStorage.setSecret(appsecret);
        wxMpService = new WxMpServiceImpl();
        wxMpService.setWxMpConfigStorage(wxMpConfigStorage);
    }

    /**
     * Get access_token does not refresh
     * @return access_token
     * @throws WxErrorException
     */
    public String getAccessToken() throws WxErrorException {
        return wxMpService.getAccessToken();
    }

In the getAccessToken method of WxMpServiceImpl, appId and appsecret are automatically stitched together in the url and a request is sent to get access_token, the source code is as follows:

public String getAccessToken(boolean forceRefresh) throws WxErrorException {
          .....
          String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential"
              + "&appid=" + wxMpConfigStorage.getAppId()
              + "&secret=" + wxMpConfigStorage.getSecret();
            HttpGet httpGet = new HttpGet(url);
            if (httpProxy != null) {
              RequestConfig config = RequestConfig.custom().setProxy(httpProxy).build();
              httpGet.setConfig(config);
            }
            CloseableHttpResponse response = getHttpclient().execute(httpGet);
            String resultContent = new BasicResponseHandler().handleResponse(response);
            WxAccessToken accessToken = WxAccessToken.fromJson(resultContent);
         .....
  }

Start project, browser inputHttp://localhost/getAccessToken, tested as follows:

@RestController
public class WechatController {
   /** Log*/
   private Logger logger = LoggerFactory.getLogger(getClass());
   /** Tool class*/
   @Autowired
   private WechatUtils wechatUtils;

   @RequestMapping("/getAccessToken")
   public void getAccessToken() {
       try {
           String accessToken = wechatUtils.getAccessToken();
           logger.info("access_token = {}", accessToken);
       } catch (WxErrorException e) {
           logger.error("Obtain access_token Failed.", e);
       }
   }
}

In addition, you can get a list of followers, information about followers, and so on.

Get user information:

@RestController
public class WechatController {
    /** Log*/
    private Logger logger = LoggerFactory.getLogger(getClass());
    /** Tool class*/
    @Autowired
    private WechatUtils wechatUtils;

    @RequestMapping("getUserInfo")
    public void getUserInfo() {
        try {
            WxMpUserList userList = wechatUtils.getUserList();
            if (userList == null || userList.getOpenIds().isEmpty()) {
                logger.warn("Focuser openId The list is empty");
                return;
            }
            List<String> openIds = userList.getOpenIds();
            logger.info("Focuser openId list = {}", openIds.toString());

            String openId = openIds.get(0);
            logger.info("Start Getting {} Basic information", openId);
            WxMpUser userInfo = wechatUtils.getUserInfo(openId);
            if (userInfo == null) {
                logger.warn("Obtain {} Basic information is empty", openId);
                return;
            }
            String city = userInfo.getCity();
            String nickname = userInfo.getNickname();
            logger.info("{} Nicknames for:{}, The cities are:{}", openId, nickname, city);
        } catch (WxErrorException e) {
            logger.error("Failed to get user message", e);
        }
    }
}

Receive messages from users

When a WeChat user sends a message to a public number, the WeChat server sends the information to the interface in our background through the URL configured in the public number background. Note that the request format at this time is a POST request, and the message message sent is in XML format. Each message type has a different XML format.

Text message

When the user sends a text message, he receives the message in the following format:

<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[fromUser]]></FromUserName>
  <CreateTime>1348831860</CreateTime>
  <MsgType><![CDATA[text]]></MsgType>
  <Content><![CDATA[this is a test]]></Content>
  <MsgId>1234567890123456</MsgId>
</xml>
parameter describe
ToUserName Developer Microsignal
FromUserName User's openId
CreateTime Message creation time (integer)
MsgType Message type, text
Content Text message content
MsgId Message id, 64-bit integer

Since the message received is in XML format, we want to parse it, but we can useJavax.xml.bindThe annotation corresponding to.Annotation maps directly to the properties of the entity class, and now creates an entity class:

/**
 * Receiving message entity
 */
@XmlRootElement(name = "xml")
@XmlAccessorType(XmlAccessType.FIELD)
public class ReceiveMsgBody {
    /**Developer Microsignal*/
    private String ToUserName;
    /** openId of the sending message user*/
    private String FromUserName;
    /** Message Creation Time*/
    private long CreateTime;
    /**Message Type*/
    private String MsgType;
    /** Message ID, judged by this field*/
    private long MsgId;
    /** Message Body of Text Message*/
    private String Content;
    // setter/getter
}

Receive the message as follows with the parameter ReceiveMsgBody:

@RestController
public class WechatController {
    /** Log*/
    private Logger logger = LoggerFactory.getLogger(getClass());
    /** Tool class*/
    @Autowired
    private WechatUtils wechatUtils;

    /**
     * Verification of WeChat Public Number Interface Configuration
     * @return
     */
    @RequestMapping(value = "/wechat", method = RequestMethod.GET)
    public String checkSignature(String signature, String timestamp, String nonce, String echostr) {
        //Step 1: Natural Sorting
        String[] tmp = {wechatUtils.getToken(), timestamp, nonce};
        Arrays.sort(tmp);
        //Step 2: sha1 Encryption
        String sourceStr = StringUtils.join(tmp);
        String localSignature = DigestUtils.sha1Hex(sourceStr);
        //Step 3: Verify the signature
        if (signature.equals(localSignature)) {
            return echostr;
        }
        return null;
    }
    /**
     * Receive User Messages
     * @param receiveMsgBody news
     * @return
     */
    @RequestMapping(value = "/wechat", method = RequestMethod.POST, produces = {"application/xml; charset=UTF-8"})
    @ResponseBody
    public Object getUserMessage(@RequestBody ReceiveMsgBody receiveMsgBody) {
        logger.info("Received message:{}", receiveMsgBody);
    }
}

Note that the request is POST and the message format is xml.

Start the project and send the message "Ha-ha" to the test number. The message received is as follows:

Picture and voice messages are also obtained.

Picture message

Message format:

<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[fromUser]]></FromUserName>
  <CreateTime>1348831860</CreateTime>
  <MsgType><![CDATA[image]]></MsgType>
  <PicUrl><![CDATA[this is a url]]></PicUrl>
  <MediaId><![CDATA[media_id]]></MediaId>
  <MsgId>1234567890123456</MsgId>
</xml>
parameter describe
MsgType Message type, image
PicUrl Picture Links (system generated)
MediaId Picture message media id, can call to get temporary material interface pull data

Voice message

Message format:

<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[fromUser]]></FromUserName>
  <CreateTime>1357290913</CreateTime>
  <MsgType><![CDATA[voice]]></MsgType>
  <MediaId><![CDATA[media_id]]></MediaId>
  <Format><![CDATA[Format]]></Format>
  <MsgId>1234567890123456</MsgId>
</xml>
parameter describe
MsgType Message type, voice
Format Voice formats, such as amr, speex, etc.
MediaId Voice message media id, can call to get temporary material interface pull data

Reuse User Messages

When a user sends a message to a public number, a POST request is generated and the developer can return a specific XML structure in the response package (Get) to respond to the message (reply text, pictures, graphics, voice, video, music are now supported).

That is, when a message is received, an XML-formatted message needs to be returned. WeChat will parse the message and push it to the user.

Reply to Text Message

The message format to be returned is as follows:

<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[fromUser]]></FromUserName>
  <CreateTime>12345678</CreateTime>
  <MsgType><![CDATA[text]]></MsgType>
  <Content><![CDATA[Hello]]></Content>
</xml>
parameter describe
ToUserName User's openId
FromUserName Developer Microsignal
CreateTime Message creation time (integer)
MsgType Message type, text
Content Text message content

This is also an entity class that creates a response message, annotated with xml:

/**
 * Response message body
 */
@XmlRootElement(name = "xml")
@XmlAccessorType(XmlAccessType.FIELD)
public class ResponseMsgBody {
    /**Receiver Account Number (OpenID Received)*/
    private String ToUserName;
    /** Developer Microsignal*/
    private String FromUserName;
    /** Message Creation Time*/
    private long CreateTime;
    /** Message Type*/
    private String MsgType;
    /** Message Body of Text Message*/
    private String Content;
    // setter/getter
}

Response message:

    /**
     * Receive message from user
     * @param receiveMsgBody news
     * @return
     */
    @RequestMapping(value = "/wechat", method = RequestMethod.POST, produces = {"application/xml; charset=UTF-8"})
    @ResponseBody
    public Object getUserMessage(@RequestBody ReceiveMsgBody receiveMsgBody) {
        logger.info("Received message:{}", receiveMsgBody);
        //Response message
        ResponseMsgBody textMsg = new ResponseMsgBody();
        textMsg.setToUserName(receiveMsgBody.getFromUserName());
        textMsg.setFromUserName(receiveMsgBody.getToUserName());
        textMsg.setCreateTime(new Date().getTime());
        textMsg.setMsgType(receiveMsgBody.getMsgType());
        textMsg.setContent(receiveMsgBody.getContent());
        return textMsg;
}

Here, ToUserName and FromUserName are the opposite of what they received, and the content is returned as is.

This way, the user will receive what he sends.

Reply to picture message

The message format to be returned is as follows:

<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[fromUser]]></FromUserName>
  <CreateTime>12345678</CreateTime>
  <MsgType><![CDATA[image]]></MsgType>
  <Image>
    <MediaId><![CDATA[media_id]]></MediaId>
  </Image>
</xml>

It is important to note that there is also a tag Image outside the MediaId tag. If it is mapped directly to an attribute of an entity class, the returned message user cannot receive it. The XmlElementWrapper annotation is needed to identify the name of the parent tag and only works on the array:

/**
 * Picture Message Response Entity Class
 */
@XmlRootElement(name = "xml")
@XmlAccessorType(XmlAccessType.FIELD)
public class ResponseImageMsg  extends ResponseMsgBody {
    /** Picture Media ID*/
    @XmlElementWrapper(name = "Image")
    private String[] MediaId;
    public String[] getMediaId() {return MediaId;}
    public void setMediaId(String[] mediaId) {MediaId = mediaId;}
}

Set response message:

ResponseVoiceMsg voiceMsg = new ResponseVoiceMsg();
voiceMsg.setToUserName(receiveMsgBody.getFromUserName());
voiceMsg.setFromUserName(receiveMsgBody.getToUserName());
voiceMsg.setCreateTime(new Date().getTime());
voiceMsg.setMsgType("voice");
voiceMsg.setMediaId(new String[]{receiveMsgBody.getMediaId()});

Reply to voice message:

The message format to be returned is as follows:

<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[fromUser]]></FromUserName>
  <CreateTime>12345678</CreateTime>
  <MsgType><![CDATA[voice]]></MsgType>
  <Voice>
    <MediaId><![CDATA[media_id]]></MediaId>
  </Voice>
</xml>

The upper tag of the MediaId tag is Voice, and the response entity class is:

/**
 * Voice Message Response Entity Class
 */
@XmlRootElement(name = "xml")
@XmlAccessorType(XmlAccessType.FIELD)
public class ResponseVoiceMsg extends ResponseMsgBody{
    /** Picture Media ID*/
    @XmlElementWrapper(name = "Voice")
    private String[] MediaId;
    public String[] getMediaId() {
        return MediaId;
    }
    public void setMediaId(String[] mediaId) {MediaId = mediaId; }
}

Set response message:

ResponseVoiceMsg voiceMsg = new ResponseVoiceMsg();
voiceMsg.setToUserName(receiveMsgBody.getFromUserName());
voiceMsg.setFromUserName(receiveMsgBody.getToUserName());
voiceMsg.setCreateTime(new Date().getTime());
voiceMsg.setMsgType(MsgType.voice.getMsgType());
voiceMsg.setMediaId(new String[]{receiveMsgBody.getMediaId()});

Here, the text message, picture message, voice message, reply text message, picture message and voice message are basically finished. Next, I will integrate the effect of the beginning of the article.

integration

Because messages are of many types, an enumeration class is defined:

/**
 * Message Type Enumeration
 */
public enum MsgType {
    text("text""Text message"),
    image("image""Picture message"),
    voice("voice""Voice message"),
    video("video""Video message"),
    shortvideo("shortvideo""Small video message"),
    location("location""Geographic location messages"),
    link("link""Link Message"),
    music("music""information about the music"),
    news("news""Teletext message"),
    ;

    MsgType(String msgType, String msgTypeDesc) {
        this.msgType = msgType;
        this.msgTypeDesc = msgTypeDesc;
    }
    private String msgType;
    private String msgTypeDesc;
    //setter/getter
    
    /**
     * Get the corresponding message type
     * @param msgType
     * @return
     */
    public static MsgType getMsgType(String msgType) {
        switch (msgType) {
            case "text":
                return text;
            case "image":
                return image;
            case "voice":
                return voice;
            case "video":
                return video;
            case "shortvideo":
                return shortvideo;
            case "location":
                return location;
            case "link":
                return link;
            case "music":
                return music;
            case "news":
                return news;
            default:
                return null;
        }
    }
}

After receiving the message, determine the type and, depending on the type, respond to different types of messages:

@RestController
public class WechatController {
    /** Log*/
    private Logger logger = LoggerFactory.getLogger(getClass());
    /** Tool class*/
    @Autowired
    private WechatUtils wechatUtils;

    /**
     * Receive User Messages
     * @param receiveMsgBody news
     * @return
     */
    @RequestMapping(value = "/wechat", method = RequestMethod.POST, produces = {"application/xml; charset=UTF-8"})
    @ResponseBody
    public Object getUserMessage(@RequestBody ReceiveMsgBody receiveMsgBody) {
        logger.info("Received message:{}", receiveMsgBody);
        MsgType msgType = MsgType.getMsgType(receiveMsgBody.getMsgType());
        switch (msgType) {
            case text:
                logger.info("The type of message received is{}", MsgType.text.getMsgTypeDesc());
                ResponseMsgBody textMsg = new ResponseMsgBody();
                textMsg.setToUserName(receiveMsgBody.getFromUserName());
                textMsg.setFromUserName(receiveMsgBody.getToUserName());
                textMsg.setCreateTime(new Date().getTime());
                textMsg.setMsgType(MsgType.text.getMsgType());
                textMsg.setContent(receiveMsgBody.getContent());
                return textMsg;
            case image:
                logger.info("The type of message received is{}", MsgType.image.getMsgTypeDesc());
                ResponseImageMsg imageMsg = new ResponseImageMsg();
                imageMsg.setToUserName(receiveMsgBody.getFromUserName());
                imageMsg.setFromUserName(receiveMsgBody.getToUserName());
                imageMsg.setCreateTime(new Date().getTime());
                imageMsg.setMsgType(MsgType.image.getMsgType());
                imageMsg.setMediaId(new String[]{receiveMsgBody.getMediaId()});
                return imageMsg;
            case voice:
                logger.info("The type of message received is{}", MsgType.voice.getMsgTypeDesc());
                ResponseVoiceMsg voiceMsg = new ResponseVoiceMsg();
                voiceMsg.setToUserName(receiveMsgBody.getFromUserName());
                voiceMsg.setFromUserName(receiveMsgBody.getToUserName());
                voiceMsg.setCreateTime(new Date().getTime());
                voiceMsg.setMsgType(MsgType.voice.getMsgType());
                voiceMsg.setMediaId(new String[]{receiveMsgBody.getMediaId()});
                return voiceMsg;
            default:
                //Other types
                break;
        }
       return null;
    }
}

This is the implementation of receiving user messages, reusing all user messages, or relatively simple; in addition to this, you can also customize the menu, focus on/cancel event monitoring, user management and other operations, after some time to slowly study.

The code has been uploaded to github, so try it out if you're interested.https://github.com/tsmyk0715/wechatproject

A Brilliant Review of the Future

Vue load optimization, double the speed.

Vue's Vuex Explanation

Setting up a Vue development environment from scratch

Ribbon LoadBalancer Source Parsing

Redis Data Structure-Dictionary Source Analysis

Redis Data Structure-String Source Analysis

Differences between domestic Dream Database and MySQL

PS: This article was first published under the Personal Public Number (Java Technology Mix). Welcome.

Tags: Programming xml Java SHA1 Vue

Posted on Sat, 06 Jun 2020 12:26:11 -0400 by ry4n0wnz