This article was first published in personal public number “Java technology hodgepodge”, welcome to pay attention to.



Recently I was looking at the development document of wechat public account, and I found it very interesting. I can customize some functions. For example, after someone pays attention to the public account, you can make a slightly more complicated reply (simple reply can be configured in the background of the public account). For example, if a follower sends a “learning” message, you can push some articles to him and send a “weather” message. You can reply to the current weather conditions. Can also be material management, user management and so on.

Today we will implement the simplest way to get messages sent by followers and reply them with the same message, supporting text messages, pictures and voice. Unlock other poses later.

Here’s the final result:

Next, the operation is mainly divided into the following steps:

  1. Apply to test the public account
  2. Configuring the Server Address
  3. Verify the validity of the server address
  4. Receive messages and reply to messages

Apply to test the public account

The first step is to apply for a test wechat public account, address:

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

Configuring the Server Address

The interface configuration information option is available on the test number page. Configure it under this option:

There are two configuration items: server address (URL), Token, and an option EncodingAESKey on the official public account

  1. URL: is the URL of the interface used by the developer to receive wechat messages and events.
  2. The Token can be filled in arbitrarily and used to generate a signature (the Token is compared with the Token contained in the interface URL to verify security).
  3. EncodingAESKey is manually filled in or randomly generated by the developer and used as the message body encryption and decryption key.

When entering the URL and Token and clicking “save”, you need to start the background and verify that the Token passes before saving, otherwise the saving will fail. Therefore, start the background code first.

Authentication Token

When filling in the URL and Token and clicking save, wechat will send wechat encrypted signature, timestamp, random number and random string echostr to the background through GET. We verify the Token according to these four parameters.

The verification steps are as follows:

  1. The token, TIMESTAMP, and nonce parameters are sorted lexicographically.
  2. The three parameter strings are concatenated into one string for SHA1 encryption.
  3. If the encrypted string is equal to signature, echostr is returned, indicating that the configuration is successful. Otherwise, NULL is returned, indicating that the configuration fails.

The following code

Properties configuration file to configure the project startup port, Token, appId and appSecret, where Token can be written as long as it is consistent with the page configuration, appId and appSecret can be seen in the test public account page. As follows:

server.port=80wechat.interface.config.token=wechattoken
wechat.appid=wxfe39186d102xxxxx
wechat.appsecret=cac2d5d68f0270ea37bd6110b26xxxxCopy the code

This configuration information is then mapped to class attributes:

@Componentpublic 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() {returnappid; } public StringgetAppsecret() {returnappsecret; }}Copy the code

Then, there needs to be a method to receive the four parameters sent from wechat, and the requested URL is the same as the page configuration needs, as follows:

@restControllerPublic class WechatController {/** log */ private Logger Logger = LoggerFactory.getLogger()); /** @autowired private WechatUtils WechatUtils; /** * verify the interface configuration of wechat public number * @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); String[] TMP = {wechatutils.getToken (), timestamp, nonce}; Arrays.sort(tmp); // Step 2: sha1 encrypt StringsourceStr = StringUtils.join(tmp);        String localSignature = DigestUtils.sha1Hex(sourceStr); // Step 3: Verify the signatureif (signature.equals(localSignature)) {            return echostr;        }        return null;    }}Copy the code

It should be noted that the request method must be GET, and POST means that wechat will send messages or events according to this URL every time the user sends a message to the public account, or generates a custom menu, or generates a wechat payment order.

Start the project, then configure the URL and Token in the test public account:

You will find that the save failed, the background received no messages, the log is not printed; Project, this is because the local start to visit address 127.0.0.1 for, and click save, is sent by the tencent server, the somebody else’s server is not visit your local natural, so you also need a network through tools, our native address mapping to the Internet, so tencent server can send messages here.

I use the tool NatApp, and after running it, it is as follows:

This Internet address http://pquigs.natappfree.cc will be mapped to our native 127.0.0.1 address, page configuration this address:

In this case, the logs are saved successfully. The following output is displayed:

Here, even if we configure the server successfully, then we can call the public number interface development.

access_token

What is the access_token? The official website is as follows:

The access_token is the global unique interface call credential of the public number. The public number needs to use the Access_token to invoke all interfaces. Developers need to keep it safe. The access_token must be stored with a minimum of 512 characters. The validity period of the Access_Token is two hours. The validity period of the access_token must be updated periodically. If the access_token is obtained repeatedly, the access_token obtained last time becomes invalid.

The interface that obtains the Access_token is limited to be invoked daily. Therefore, the interface does not re-obtain the Access_token every time it is invoked. Instead, it is cached after obtaining the access_token.

The official document: https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html

Because the development interface of calling public account is basically XML format message, so for simple development, we can use the open source tool Weixin-Java-mp for development, and add related dependencies to the POm. XML file as follows:

< the 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> The < version > 1.3.3 < / version > < / dependency >Copy the code

In weixin – Java – mp has two important classes, one is the configuration related WxMpInMemoryConfigStorage, one is the call related WxMpServiceImpl WeChat interface.

Get access_token

Now get the access_token:

  1. The interface address is:
HTTPS requests: https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRETCopy the code
  1. Parameters that
parameter Whether must instructions
grant_type is Get access_token fill client_credential
appid is Unique credentials of third-party users
secret is The unique credential key of the third-party user is AppSecret
  1. The packet is returned in JSON format:
{"access_token":"ACCESS_TOKEN"."expires_in": 7200}Copy the code
The following code

First of all in the beginning of the article tools WechatUtils initialization WxMpInMemoryConfigStorage and WxMpServiceImpl, Then call WxMpServiceImpl’s getAccessToken() method to get the access_token:

@componentPublic class WechatUtils {@componentPublic 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() {returntoken; } public StringgetAppid() {returnappid; } public StringgetAppsecret() {returnappsecret; } /** * private WxMpService WxMpService; /** */ @postconstruct private voidinit() { WxMpInMemoryConfigStorage wxMpConfigStorage = new WxMpInMemoryConfigStorage(); wxMpConfigStorage.setAppId(appid); wxMpConfigStorage.setSecret(appsecret); wxMpService = new WxMpServiceImpl(); wxMpService.setWxMpConfigStorage(wxMpConfigStorage); } /** * The access_token is not refreshed * @return access_token     * @throws WxErrorException     */    public String getAccessToken() throws WxErrorException {        returnwxMpService.getAccessToken(); }}Copy the code

WxMpServiceImpl’s getAccessToken method automatically concatenates appId and AppSecret in the URL, and then sends a request to obtain access_token.

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); . }Copy the code

Start the project, the browser type http://localhost/getAccessToken, test as follows:

@restControllerPublic class WechatController {/** log */ private Logger Logger = LoggerFactory.getLogger()); /** @autowired private WechatUtils WechatUtils; @RequestMapping("/getAccessToken")   public void getAccessToken() {       try {           String accessToken = wechatUtils.getAccessToken();           logger.info("access_token = {}", accessToken);       } catch (WxErrorException e) {           logger.error("Failed to obtain access_token.", e); }}}Copy the code

In addition, you can also get the list of followers, followers’ information, etc.

Get user information:

@restControllerPublic class WechatController {/** log */ private Logger Logger = LoggerFactory.getLogger()); /** @autowired private WechatUtils WechatUtils; @RequestMapping("getUserInfo")    public void getUserInfo() {        try {            WxMpUserList userList = wechatUtils.getUserList();            if (userList == null || userList.getOpenIds().isEmpty()) {                logger.warn("Follower openId list is empty");                return;            }            List<String> openIds = userList.getOpenIds();            logger.info("Follower openId list = {}", openIds.toString());            String openId = openIds.get(0);            logger.info("Start getting basic information about {}", openId);            WxMpUser userInfo = wechatUtils.getUserInfo(openId);            if (userInfo == null) {                logger.warn("Getting basic information for {} is empty", openId);                return;            }            String city = userInfo.getCity();            String nickname = userInfo.getNickname();            logger.info("{} nickname: {}, city: {}", openId, nickname, city);        } catch (WxErrorException e) {            logger.error("Failed to get user message", e); }}}Copy the code

Receives messages sent by users

When wechat users send messages to the public account, the wechat server will send the information to the interface of our background through the URL configured in the background of the public account. Note that the request format at this time is POST request, and the message packet format is XML, and the XML format of each message type is different.

A text message

When the user sends a text message, the packet is received in the following format:

<xml> <ToUserName><! [CDATA[toUser]]></ToUserName> <FromUserName><! [CDATA[fromUser]]></FromUserName> <CreateTime>1348831860</CreateTime> <MsgType><! [CDATA[text]]></MsgType> <Content><! [CDATA[this is atest]]></Content>  <MsgId>1234567890123456</MsgId></xml>Copy the code
Parameter Description ToUserName Developer wechat id FromUserName User openIdCreateTime message creation time (integer) MsgType Message type, textContent Message content MsgId message ID, 64-bit integerCopy the code

Due to the received message is an XML format, so we’re going to resolve, but you can use the javax.mail. XML. Bind. The annotation corresponding annotations directly mapping the physical attributes of a class, now create an entity class:

/** * Receive message entity */ @xmlRootelement (name ="xml") @xmlAccessorType (XmlAccessType.FIELD)public Class ReceiveMsgBody {/** developer wechat */ private String ToUserName; /** openId */ private String FromUserName; /** private long CreateTime; /** Message type */ private String MsgType; Private long MsgId; /* private long MsgId; /** Text message body */ private String Content; // setter/getter}Copy the code

The method for receiving messages is as follows. The parameter is ReceiveMsgBody:

@restControllerPublic class WechatController {/** log */ private Logger Logger = LoggerFactory.getLogger()); /** @autowired private WechatUtils WechatUtils; /** * verify the interface configuration of wechat public number * @return     */    @RequestMapping(value = "/wechat", method = RequestMethod.GET) public String checkSignature(String signature, String timestamp, String nonce, String[] TMP = {wechatutils.getToken (), timestamp, nonce}; Arrays.sort(tmp); // Step 2: sha1 encrypt StringsourceStr = StringUtils.join(tmp);        String localSignature = DigestUtils.sha1Hex(sourceStr); // Step 3: Verify the signatureif (signature.equals(localSignature)) {            return echostr;        }        returnnull; } /** * Receive user messages * @param receiveMsgBody message * @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);    }}Copy the code

Notice that the request mode is POST and the packet format is XML.

Start the project, send the message “ha ha” to the test number, and receive the following message:

Picture messages and voice messages are also retrieved.

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>Copy the code
Parameter description MsgType message type, imagePicUrl image link (generated by the system) MediaId image message MediaId. You can call the get temporary material interface to pull dataCopy the code

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>Copy the code
Parameter description MsgType message type, voiceFormat voiceFormat, such as amr, speex MediaId voice message MediaId. You can call the obtain temporary material interface to pull dataCopy the code

Replying to user messages

When a user sends a message to a public account, a POST request is generated, and the developer can respond to the message by returning a specific XML structure in the response packet (Get) (text, image, text, voice, video, and music are now supported).

In other words, after receiving the message, you need to send back an XML message. Wechat will parse the message and then push the message to the user.

Reply text message

The format of the packet to be returned is as follows:

<xml> <ToUserName><! [CDATA[toUser]]></ToUserName> <FromUserName><! [CDATA[fromUser]]></FromUserName> <CreateTime>12345678</CreateTime> <MsgType><! [CDATA[text]]></MsgType> <Content><! [CDATA [you]] > < / Content > > < / XMLCopy the code

Parameter Description ToUserName User openIdFromUserName Developer wechat id CreateTime Message creation time (integer) MsgType Message type, textContent Text message contentCopy the code

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

/** * Response message body */ @xmlRootelement (name ="xml") @xmlAccessorType (XmlAccessType.FIELD)public class ResponseMsgBody {/** Receiver account (received OpenID) */ private String ToUserName; /** private String FromUserName; /** private long CreateTime; /** Message type */ private String MsgType; /** Text message body */ private String Content; // setter/getter}Copy the code

Response message:

/** * Receive messages from users * @param receiveMsgBody message * @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); TextMsg = new ResponseMsgBody(); textMsg.setToUserName(receiveMsgBody.getFromUserName()); textMsg.setFromUserName(receiveMsgBody.getToUserName()); textMsg.setCreateTime(new Date().getTime()); textMsg.setMsgType(receiveMsgBody.getMsgType()); textMsg.setContent(receiveMsgBody.getContent());returntextMsg; }Copy the code

Here the ToUserName and FromUserName are reversed in the received message and the content is returned as is.

In this way, users receive what they send.

Reply to picture message

The format of the packet 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>Copy the code

If the MediaId tag is mapped directly to an attribute of the entity class, the returned message will not be received by the user. In this case, the XmlElementWrapper annotation will be used to identify the name of the parent tag, which can only be used on arrays:

/** * Picture message response entity class */ @xmlRootelement (name ="xml") @xmlAccessorType (XmlAccessType.FIELD)public Class ResponseImageMsg extends ResponseMsgBody {/** Image media ID */ @XmlElementWrapper(name ="Image")    private String[] MediaId;    public String[] getMediaId() {returnMediaId; } public voidsetMediaId(String[] mediaId) {MediaId = mediaId; }}Copy the code

Set the response message:

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()});Copy the code

Reply to a voice message:

The format of the packet 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>Copy the code

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{/** image media ID */ @XmlElementWrapper(name ="Voice")    private String[] MediaId;    public String[] getMediaId() {        return MediaId;    }    public void setMediaId(String[] mediaId) {MediaId = mediaId; }}Copy the code

Set the 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()});Copy the code

At this point, receiving text message, picture message, voice message, reply text message, picture message, voice message is basically done, let’s integrate to achieve the effect of the beginning of the article.

integration

Since messages are of many types, define an enumerated class:

/** * public enum MsgType {text("text"."Text message"),    image("image"."Picture message"),    voice("voice"."Voice message"),    video("video"."Video message"),    shortvideo("shortvideo"."Little video message"),    location("location"."Geo-location message"),    link("link"."Link message"),    music("music"."Music message"),    news("news"."Text 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:                returnnull; }}}Copy the code

Then after receiving the message, determine the type, according to the different type, reply to different types of message is ok:

@restControllerPublic class WechatController {/** log */ private Logger Logger = LoggerFactory.getLogger()); /** @autowired private WechatUtils WechatUtils; /** * Receive user messages * @param receiveMsgBody message * @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("Received message type {}", 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("Received message type {}", 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("Received message type {}", 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()});                returnvoiceMsg; Default: // Other typesbreak;        }       return null;    }}Copy the code

This is the realization of receiving the user message, reply to the user message all achieved, or relatively simple; In addition to this, you can also customize the menu, follow/cancel event listening processing, user management and other operations, later have time to study slowly.

The code has been uploaded to Github, so if you’re interested, give it a try. https://github.com/tsmyk0715/wechatproject


Highlights from the past


Vue load optimization, double the speed.


The Vuex of Vue is explained in detail


Build a Vue development environment from scratch


Ribbon LoadBalancer – LoadBalancer


Redis data structure – dictionary source code analysis


Redis data structure – string source code analysis


The difference between domestic Dameng database and MySQL