公衆號後臺開發(SpingMVC接收與響應公衆號消息)

1.準備

1.準備服務

與微信對接的url要具有如下條件:html

(1)在公網上可以訪問java

(2)端口只支持80端口git

  在這裏若是是公網可以訪問的服務最好,也能夠經過花生殼或者其餘外網映射工具進行映射,好比ngrokgithub

2.數據交互原理

  開發模式與編輯模式是互斥的,打開開發模式的時候,編輯模式的自動回覆與自定義菜單失效;打開編輯模式的自動回覆或者自定義菜單的時候開發模式會失效。web

 

 

開發模式的數據交互原理以下:面試

 

  咱們須要開發的任務就是維信公衆號服務器,包括業務邏輯、身份驗證等操做。spring

2.接入後臺

  參考公衆號開發文檔:   開發->開發者工具-》開發者文檔,裏面有相似於對接釘釘的文檔,有接入指南以及其餘接口文檔。數據庫

  https://developers.weixin.qq.com/doc/offiaccount/Getting_Started/Overview.htmlapache

1.  第一步:填寫服務器配置

  到公衆號後臺: https://mp.weixin.qq.com/springboot

 

 

2 第二步:驗證消息的確來自微信服務器(在本身的微信服務器進行驗證)

  開發者提交信息後,微信服務器將發送GET請求到填寫的服務器地址URL上,GET請求攜帶參數以下表所示:

 

開發者經過檢驗signature對請求進行校驗(下面有校驗方式)。若確認這次GET請求來自微信服務器,請原樣返回echostr參數內容,則接入生效,成爲開發者成功,不然接入失敗。加密/校驗流程以下:

1)將token、timestamp、nonce三個參數進行字典序排序 2)將三個參數字符串拼接成一個字符串進行sha1加密 3)開發者得到加密後的字符串可與signature對比,標識該請求來源於微信

SpringMVC接收代碼以下:

package cn.qlq.controller.weixin;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import cn.qlq.controller.UserController;
import cn.qlq.utils.weixin.WeixinCheckUtils;

@Controller
@RequestMapping("weixin")
public class WeixinController {

    private static final Logger logger = LoggerFactory.getLogger(UserController.class);

    @ResponseBody
    @RequestMapping("index")
    public String index(@RequestParam(required = false) String signature,
            @RequestParam(required = false) String timestamp, @RequestParam(required = false) String nonce,
            @RequestParam(required = false) String echostr) {

        logger.debug("signature: {}, timestamp: {}, nonce: {}, echostr: {}", signature, timestamp, nonce, echostr);

        if (StringUtils.isNoneBlank(signature, timestamp, nonce)
                && WeixinCheckUtils.checkSignature(signature, timestamp, nonce)) {
            return echostr;
        }

        return "error";
    }
}

 

驗證工具以下:

package cn.qlq.utils.weixin;

import java.security.MessageDigest;
import java.util.Arrays;

public class WeixinCheckUtils {

    // token,與微信公衆號後臺的一致
    private static final String token = "devqiaolq";

    public static boolean checkSignature(String signature, String timestamp, String nonce) {
        String[] arr = new String[] { token, timestamp, nonce };

        // 排序
        Arrays.sort(arr);

        // 生成字符串
        StringBuffer content = new StringBuffer();
        for (int i = 0; i < arr.length; i++) {
            content.append(arr[i]);
        }

        // sha1加密
        String temp = getSha1(content.toString());
        return temp.equals(signature);

    }

    public static String getSha1(String str) {
        if (str == null || str.length() == 0) {
            return null;
        }

        char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };

        try {
            MessageDigest mdTemp = MessageDigest.getInstance("SHA1");
            mdTemp.update(str.getBytes("UTF-8"));

            byte[] md = mdTemp.digest();
            int j = md.length;
            char buf[] = new char[j * 2];
            int k = 0;
            for (int i = 0; i < j; i++) {
                byte byte0 = md[i];
                buf[k++] = hexDigits[byte0 >>> 4 & 0xf];
                buf[k++] = hexDigits[byte0 & 0xf];
            }
            return new String(buf);
        } catch (Exception e) {
            return null;
        }
    }
}

 

  注意:若是有登陸過濾器,記得在過濾器中放行微信請求.

 

3. 第三步:依據接口文檔實現業務邏輯

1. 首先須要啓用開發者模式:(啓用開發者模式以後本身的自定義菜單就不會生效)

2. 接收與響應文字消息

  當普通微信用戶向公衆帳號發消息時,微信服務器將POST消息的XML數據包到開發者填寫的URL上。

文本消息的xml數據格式以下:

<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>

 

參數解釋:

參數 描述
ToUserName 開發者微信號
FromUserName 發送方賬號(一個OpenID)
CreateTime 消息建立時間 (整型)
MsgType 消息類型,文本爲text
Content 文本消息內容
MsgId 消息id,64位整型

 

(1)創建後臺對應的TextMessage實體類:

package cn.qlq.bean.weixin;

public class TextMessage {

    /**
     * 開發者微信號
     */
    private String ToUserName;

    /**
     * 發送方賬號(一個OpenID)
     */
    private String FromUserName;

    /**
     * 消息建立時間 (整型)
     */
    private long CreateTime;

    /**
     * text
     */
    private String MsgType;

    /**
     * 文本消息內容
     */
    private String Content;

    /**
     * 消息id,64位整型
     */
    private String MsgId;

    @Override
    public String toString() {
        return "TextMessage{" + "ToUserName='" + ToUserName + '\'' + ", FromUserName='" + FromUserName + '\''
                + ", CreateTime=" + CreateTime + ", MsgType='" + MsgType + '\'' + ", Content='" + Content + '\''
                + ", MsgId='" + MsgId + '\'' + '}';
    }

    public String getToUserName() {
        return ToUserName;
    }

    public void setToUserName(String toUserName) {
        ToUserName = toUserName;
    }

    public String getFromUserName() {
        return FromUserName;
    }

    public void setFromUserName(String fromUserName) {
        FromUserName = fromUserName;
    }

    public long getCreateTime() {
        return CreateTime;
    }

    public void setCreateTime(long createTime) {
        CreateTime = createTime;
    }

    public String getMsgType() {
        return MsgType;
    }

    public void setMsgType(String msgType) {
        MsgType = msgType;
    }

    public String getContent() {
        return Content;
    }

    public void setContent(String content) {
        Content = content;
    }

    public String getMsgId() {
        return MsgId;
    }

    public void setMsgId(String msgId) {
        MsgId = msgId;
    }

}

 

(2)編寫工具類實現xml(接收的是xml格式的數據)轉map和TextMessage對象轉換成xml(響應數據格式爲xml)

pom加入:

        <!--微信轉換XML所需包 -->
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.6.1</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.thoughtworks.xstream/xstream -->
        <dependency>
            <groupId>com.thoughtworks.xstream</groupId>
            <artifactId>xstream</artifactId>
            <version>1.4.10</version>
        </dependency>

 

工具類:

package cn.qlq.utils.weixin;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import com.thoughtworks.xstream.XStream;

import cn.qlq.bean.weixin.TextMessage;

public class MessageUtils {

    /**
     * xml數據轉map
     *
     * @param request
     * @return
     * @throws IOException
     * @throws DocumentException
     */
    public static Map<String, String> xmlToMap(HttpServletRequest request) throws IOException, DocumentException {
        Map<String, String> map = new HashMap<>();
        SAXReader reader = new SAXReader();

        InputStream inputStream = request.getInputStream();
        Document document = reader.read(inputStream);

        Element root = document.getRootElement();
        List<Element> list = root.elements();

        for (Element element : list) {
            map.put(element.getName(), element.getText());
        }

        inputStream.close();
        return map;
    }

    /**
     * 將文本消息對象轉換成xml
     *
     * @param textMessage
     * @return
     */
    public static String textMessageToXml(TextMessage textMessage) {
        XStream xStream = new XStream();
        // 將xml的根元素替換成xml
        xStream.alias("xml", textMessage.getClass());
        return xStream.toXML(textMessage);
    }
}

 

(3)重寫Controller接收消息和響應消息

package cn.qlq.controller.weixin;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.dom4j.DocumentException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import cn.qlq.bean.weixin.TextMessage;
import cn.qlq.controller.UserController;
import cn.qlq.utils.weixin.MessageUtils;
import cn.qlq.utils.weixin.WeixinCheckUtils;

@Controller
@RequestMapping("weixin")
public class WeixinController {

    private static final Logger logger = LoggerFactory.getLogger(UserController.class);

    @RequestMapping(value = "index", method = { RequestMethod.GET, RequestMethod.POST })
    public void index(HttpServletRequest request, HttpServletResponse response) throws IOException {

        // 將請求、響應的編碼均設置爲UTF-8(防止中文亂碼)
        request.setCharacterEncoding("UTF-8"); // 微信服務器POST消息時用的是UTF-8編碼,在接收時也要用一樣的編碼,不然中文會亂碼;
        response.setCharacterEncoding("UTF-8"); // 在響應消息(回覆消息給用戶)時,也將編碼方式設置爲UTF-8,原理同上;

        String method = request.getMethod().toLowerCase();
        logger.info("method: {}", method);

        // 驗證是不是微信請求
        if ("get".equals(method)) {
            doGet(request, response);
            return;
        }

        // POST請求接收消息,且給客戶響應消息
        doPost(request, response);
    }

    /**
     * Post請求用於接收消息且處理消息以後回傳消息
     * 
     * @param request
     * @param response
     * @throws IOException
     */
    private void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
        PrintWriter out = response.getWriter();
        try {
            Map<String, String> map = MessageUtils.xmlToMap(request);
            String fromUserName = map.get("FromUserName");
            String toUserName = map.get("ToUserName");
            String msgType = map.get("MsgType");
            String content = map.get("Content");
            logger.info("map: {}", map);

            if (StringUtils.isNotBlank(content)) {
                System.out.println("接收的的消息爲:" + content + ",你能夠根據關鍵字進行搜索或者作其餘");
            }

            String message = null;
            if ("text".equals(msgType)) {
                TextMessage textMessage = new TextMessage();
                // 回傳消息,因此講fromuser和toUser交換
                textMessage.setFromUserName(toUserName);
                textMessage.setToUserName(fromUserName);
                textMessage.setMsgType(msgType);
                textMessage.setCreateTime(new Date().getTime());
                textMessage.setContent("您發送的消息爲: " + content);
                logger.info("textMessage: {}", textMessage);

                message = MessageUtils.textMessageToXml(textMessage);
            }

            out.print(message);// 把消息發送到客戶端
        } catch (DocumentException e) {
            logger.error("dispose post request error", e);
        } finally {
            out.close();
        }
    }

    /**
     * Get請求用於微信配置驗證
     * 
     * @param request
     * @param response
     * @throws IOException
     */
    private void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
        String signature = request.getParameter("signature");// 微信加密簽名
        String timestamp = request.getParameter("timestamp");// 時間戳
        String nonce = request.getParameter("nonce");// 隨機數
        String echostr = request.getParameter("echostr");// 隨機字符串
        logger.info("signature: {}, timestamp: {}, nonce: {}, echostr: {}", signature, timestamp, nonce, echostr);

        if (StringUtils.isNoneBlank(signature, timestamp, nonce)
                && WeixinCheckUtils.checkSignature(signature, timestamp, nonce)) {
            response.getWriter().write(echostr);
        }
    }
}

 

測試:

 

Java服務器日誌以下:

2019-10-23 23:34:15.625 INFO 244500 --- [nio-8088-exec-9] cn.qlq.controller.UserController : method: post
2019-10-23 23:34:15.633 INFO 244500 --- [nio-8088-exec-9] cn.qlq.controller.UserController : map: {MsgId=22503405793257008, FromUserName=o_qAo0u6Snhoc7Z45RfSxYatMWpo, CreateTime=1571844638, Content=怎麼了, ToUserName=gh_fc4bd5c2fda8, MsgType=text}
接收的的消息爲:怎麼了,你能夠根據關鍵字進行搜索或者作其餘
2019-10-23 23:34:15.635 INFO 244500 --- [nio-8088-exec-9] cn.qlq.controller.UserController : textMessage: TextMessage{ToUserName='o_qAo0u6Snhoc7Z45RfSxYatMWpo', FromUserName='gh_fc4bd5c2fda8', CreateTime=1571844855635, MsgType='text', Content='您發送的消息爲: 怎麼了', MsgId='null'}
2019-10-23 23:34:20.288 INFO 244500 --- [nio-8088-exec-3] cn.qlq.controller.UserController : method: post
2019-10-23 23:34:20.295 INFO 244500 --- [nio-8088-exec-3] cn.qlq.controller.UserController : map: {MsgId=22503406289173072, FromUserName=o_qAo0u6Snhoc7Z45RfSxYatMWpo, CreateTime=1571844642, Content=什麼意思, ToUserName=gh_fc4bd5c2fda8, MsgType=text}
接收的的消息爲:什麼意思,你能夠根據關鍵字進行搜索或者作其餘
2019-10-23 23:34:20.296 INFO 244500 --- [nio-8088-exec-3] cn.qlq.controller.UserController : textMessage: TextMessage{ToUserName='o_qAo0u6Snhoc7Z45RfSxYatMWpo', FromUserName='gh_fc4bd5c2fda8', CreateTime=1571844860296, MsgType='text', Content='您發送的消息爲: 什麼意思', MsgId='null'}

 

4. 接收其餘類型的消息以及返回文本消息、圖文消息

  公衆號能夠接受的消息大概有文本消息(在上面已經接收)、圖片(image)消息、語音(voice,在後臺接口開啓語音識別能夠自動識別文字)消息、視頻(video)消息、短視頻(shortvideo)消息、地理位置(location)消息、連接(link)消息、事件(event,事件類型:subscribe(訂閱)、unsubscribe(取消訂閱))消息。

  公衆號能夠回覆的消息有: 文本(text)消息、圖片消息(image)、語音消息(voice)、視頻消息(video)、音樂消息(music)、圖文(news)消息。

 

注意:這裏須要注意接收的消息和發送的消息數據數據格式不同。以下是回覆的圖片消息和圖文消息的數據格式:

回傳圖片消息數據格式:

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

 

回傳圖文消息數據格式:

<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[fromUser]]></FromUserName>
  <CreateTime>12345678</CreateTime>
  <MsgType><![CDATA[news]]></MsgType>
  <ArticleCount>1</ArticleCount>
  <Articles>
    <item>
      <Title><![CDATA[title1]]></Title>
      <Description><![CDATA[description1]]></Description>
      <PicUrl><![CDATA[picurl]]></PicUrl>
      <Url><![CDATA[url]]></Url>
    </item>
  </Articles>
</xml>

 

本身封裝的controller接收數據和消息處理的工具類以下:

package cn.qlq.controller.weixin;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.dom4j.DocumentException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import cn.qlq.bean.weixin.response.AbstractResponseMessage;
import cn.qlq.controller.UserController;
import cn.qlq.utils.weixin.MessageHandler;
import cn.qlq.utils.weixin.MessageUtils;
import cn.qlq.utils.weixin.WeixinCheckUtils;

@Controller
@RequestMapping("weixin")
public class WeixinController {

    private static final Logger logger = LoggerFactory.getLogger(UserController.class);

    @RequestMapping(value = "index", method = { RequestMethod.GET, RequestMethod.POST })
    public void index(HttpServletRequest request, HttpServletResponse response) throws IOException {

        // 將請求、響應的編碼均設置爲UTF-8(防止中文亂碼)
        request.setCharacterEncoding("UTF-8"); // 微信服務器POST消息時用的是UTF-8編碼,在接收時也要用一樣的編碼,不然中文會亂碼;
        response.setCharacterEncoding("UTF-8"); // 在響應消息(回覆消息給用戶)時,也將編碼方式設置爲UTF-8,原理同上;

        String method = request.getMethod().toLowerCase();
        logger.info("method: {}", method);

        // 驗證是不是微信請求
        if ("get".equals(method)) {
            doGet(request, response);
            return;
        }

        // POST請求接收消息,且給客戶響應消息
        doPost(request, response);
    }

    /**
     * Post請求用於接收消息且處理消息以後回傳消息
     * 
     * @param request
     * @param response
     * @throws IOException
     */
    private void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
        PrintWriter out = response.getWriter();
        try {
            // 消息轉map
            Map<String, Object> map = MessageUtils.xmlToMap(request);
            logger.info("接收到的消息map: {}", map);

            // 調用工具類處理完以後顯示回傳消息
            AbstractResponseMessage responseMessage = MessageHandler.handlMessage(map);
            logger.info("回傳的消息responseMessage: {}", responseMessage);

            if (responseMessage == null) {
                return;
            }

            String messageToXml = MessageUtils.messageToXml(responseMessage);
            logger.info("回傳的消息responseMessage messageToXml: {}", messageToXml);
            out.print(messageToXml);// 把消息發送到客戶端
        } catch (DocumentException e) {
            logger.error("dispose post request error", e);
        } finally {
            out.close();
        }
    }

    /**
     * Get請求用於微信配置驗證
     * 
     * @param request
     * @param response
     * @throws IOException
     */
    private void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
        String signature = request.getParameter("signature");// 微信加密簽名
        String timestamp = request.getParameter("timestamp");// 時間戳
        String nonce = request.getParameter("nonce");// 隨機數
        String echostr = request.getParameter("echostr");// 隨機字符串
        logger.info("signature: {}, timestamp: {}, nonce: {}, echostr: {}", signature, timestamp, nonce, echostr);

        if (StringUtils.isNoneBlank(signature, timestamp, nonce)
                && WeixinCheckUtils.checkSignature(signature, timestamp, nonce)) {
            response.getWriter().write(echostr);
        }
    }
}

 

package cn.qlq.utils.weixin;

import java.util.Date;
import java.util.Map;

import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import cn.qlq.bean.weixin.EventMessage;
import cn.qlq.bean.weixin.ImageMessage;
import cn.qlq.bean.weixin.LinkMessage;
import cn.qlq.bean.weixin.LocationMessage;
import cn.qlq.bean.weixin.TextMessage;
import cn.qlq.bean.weixin.VideoMessage;
import cn.qlq.bean.weixin.VoiceMessage;
import cn.qlq.bean.weixin.response.AbstractResponseMessage;
import cn.qlq.bean.weixin.response.ImageResponseMessage;
import cn.qlq.bean.weixin.response.NewsResponseMessage;
import cn.qlq.bean.weixin.response.NewsResponseMessageArticle;
import cn.qlq.bean.weixin.response.NewsResponseMessageArticleItem;
import cn.qlq.bean.weixin.response.TextResponseMessage;
import cn.qlq.utils.BeanUtils;

public class MessageHandler {

    public static final String MESSAGE_TEXT = "text";
    public static final String MESSAGE_IMAGE = "image";
    public static final String MESSAGE_VOICE = "voice";
    public static final String MESSAGE_VIDEO = "video";
    public static final String MESSAGE_LINK = "link";
    public static final String MESSAGE_LOCATION = "location";
    public static final String MESSAGE_EVENT = "event";// 事件
    public static final String MESSAGE_SUBSCRIBE = "subscribe";// 關注
    public static final String MESSAGE_UNSUBSCRIBE = "unsubscribe";// 取消關注
    public static final String MESSAGE_CLICK = "CLICK";
    public static final String MESSAGE_VIEW = "VIEW";

    private static final Logger logger = LoggerFactory.getLogger(MessageHandler.class);

    public static AbstractResponseMessage handlMessage(Map<String, Object> messageMap) {
        if (MapUtils.isEmpty(messageMap)) {
            logger.error("message is empty");
            return null;
        }

        String msgType = (String) messageMap.get("MsgType");
        if (MESSAGE_TEXT.equals(msgType)) {
            return handleTextMessage(messageMap);
        } else if (MESSAGE_IMAGE.equals(msgType)) {
            return handleImageMessage(messageMap);
        } else if (MESSAGE_LOCATION.equals(msgType)) {
            return handleLocationMessage(messageMap);
        } else if (MESSAGE_EVENT.equals(msgType)) {
            return handleEventMessage(messageMap);
        } else if (MESSAGE_LINK.equals(msgType)) {
            return handleLinkMessage(messageMap);
        } else if (MESSAGE_VOICE.equals(msgType)) {
            return handleVoiceMessage(messageMap);
        } else if (MESSAGE_VIDEO.equals(msgType)) {
            return handleVideoMessage(messageMap);
        }

        return null;
    }

    private static AbstractResponseMessage handleVideoMessage(Map<String, Object> messageMap) {
        VideoMessage message = BeanUtils.map2Bean(messageMap, VideoMessage.class, true);

        String thumbMediaId = message.getThumbMediaId();
        // 能夠用圖片路徑作其餘操做
        if (StringUtils.isNotBlank(thumbMediaId)) {
            System.out.println("您接收到視頻消息, thumbMediaId爲: " + thumbMediaId);
        }

        String responseMsg = "您發送了一條視頻消息,thumbMediaId爲: " + thumbMediaId;
        return MessageUtils.initTextMessage(message.getToUserName(), message.getFromUserName(), responseMsg);
    }

    private static AbstractResponseMessage handleVoiceMessage(Map<String, Object> messageMap) {
        VoiceMessage message = BeanUtils.map2Bean(messageMap, VoiceMessage.class, true);

        String recognition = message.getRecognition();
        String format = message.getFormat();
        // 能夠用圖片路徑作其餘操做
        if (StringUtils.isNotBlank(recognition)) {
            System.out.println("您接收到語音消息, 格式爲: " + format + ", 轉換後的文字爲: " + recognition);
        }

        String responseMsg = "您發送了一條語音消息,格式爲: " + format + ", 轉換後的文字爲: " + recognition;
        return MessageUtils.initTextMessage(message.getToUserName(), message.getFromUserName(), responseMsg);
    }

    /**
     * 處理連接消息(回覆一條圖文消息)
     * 
     * @param message
     * @return
     */
    private static AbstractResponseMessage handleLinkMessage(Map<String, Object> messageMap) {
        LinkMessage message = BeanUtils.map2Bean(messageMap, LinkMessage.class, true);

        String desc = message.getDescription();
        String title = message.getTitle();
        String url = message.getUrl();
        // 能夠用圖片路徑作其餘操做
        if (StringUtils.isNotBlank(url)) {
            System.out.println("您接收到連接消息, title: " + title + ", desc: " + desc + ", url: " + url);
        }

        // 回覆一條圖文消息
        NewsResponseMessage news = new NewsResponseMessage();
        news.setCreateTime(System.currentTimeMillis());
        news.setFromUserName(message.getToUserName());
        news.setToUserName(message.getFromUserName());
        news.setArticleCount("1");
        news.setMsgType("news");

        NewsResponseMessageArticle article = new NewsResponseMessageArticle();
        news.setArticles(article);

        // 建立多條圖文消息
        for (int i = 0; i < 1; i++) {
            NewsResponseMessageArticleItem item = new NewsResponseMessageArticleItem();
            item.setTitle("18年寫的面試心得");
            item.setPicUrl("https://images.cnblogs.com/cnblogs_com/qlqwjy/1031659/o_9.bmp");
            item.setUrl("https://www.cnblogs.com/qlqwjy/p/9194434.html");
            item.setDescription("18年畢設心血來潮寫的畢設心得,1年後再看有點東西。");

            article.addNewsResponseMessageArticleItem(item);
        }

        return news;
    }

    /**
     * 處理事件消息(訂閱和取消訂閱)
     * 
     * @param messageMap
     * @return
     */
    private static AbstractResponseMessage handleEventMessage(Map<String, Object> messageMap) {
        EventMessage message = BeanUtils.map2Bean(messageMap, EventMessage.class, true);

        String event = message.getEvent();
        if (StringUtils.isNotBlank(event)) {
            System.out.println("您接收到事件消息, 事件類型爲: " + event);
        }

        if (MESSAGE_SUBSCRIBE.equals(event)) {
            // 關注的時候
            System.out.println("這裏能夠向數據庫插入數據");

            String responseMsg = MessageUtils.subscribeWelcomeText();
            return MessageUtils.initTextMessage(message.getToUserName(), message.getFromUserName(), responseMsg);
        } else {
            // 取消關注(不用回傳消息.須要將用戶產生的數據刪除)
            System.out.println("這時須要從數據刪除  " + message.getFromUserName() + " 用戶產生的相關數據");

            return null;
        }
    }

    private static AbstractResponseMessage handleLocationMessage(Map<String, Object> messageMap) {
        LocationMessage message = BeanUtils.map2Bean(messageMap, LocationMessage.class, true);

        String label = message.getLabel();
        if (StringUtils.isNotBlank(label)) {
            System.out.println("您接收到位置消息, 地理位置信息爲: " + message);
        }

        String responseMsg = "您發送了一條位置消息, 您的地理位置信息爲:" + label;
        return MessageUtils.initTextMessage(message.getToUserName(), message.getFromUserName(), responseMsg);
    }

    /**
     * 處理圖片消息(回覆一條圖片消息)
     * 
     * @param message
     * @return
     */
    private static AbstractResponseMessage handleImageMessage(Map<String, Object> message) {
        ImageMessage imageMessage = BeanUtils.map2Bean(message, ImageMessage.class, true);

        String url = imageMessage.getPicUrl();
        // 能夠用圖片路徑作其餘操做
        if (StringUtils.isNotBlank(url)) {
            System.out.println("您接收到的圖片消息url爲: " + url);
        }

        // 回傳一條圖片消息
        ImageResponseMessage responseMessage = new ImageResponseMessage();
        responseMessage.setCreateTime(System.currentTimeMillis());
        responseMessage.setFromUserName(imageMessage.getToUserName());
        responseMessage.setToUserName(imageMessage.getFromUserName());
        responseMessage.setMediaId(imageMessage.getMediaId());
        responseMessage.setMsgType(MESSAGE_IMAGE);

        return responseMessage;
    }

    /**
     * 處理文本消息
     * 
     * @param message
     * @return
     */
    private static AbstractResponseMessage handleTextMessage(Map<String, Object> message) {
        TextMessage textMessage = BeanUtils.map2Bean(message, TextMessage.class, true);

        String content = textMessage.getContent();
        // 能夠根據文本消息去查庫或者進行其餘操做
        if (StringUtils.isNotBlank(content)) {
            System.out.println("您接收到的文本消息內容爲: " + content);
        }

        // 設置回傳的消息內容
        TextResponseMessage responseMessage = new TextResponseMessage();
        responseMessage.setContent("服務器已接收到您的消息,內容爲: " + content);
        responseMessage.setCreateTime(new Date().getTime());
        responseMessage.setFromUserName(textMessage.getToUserName());
        responseMessage.setToUserName(textMessage.getFromUserName());
        responseMessage.setMsgType(MESSAGE_TEXT);

        return responseMessage;
    }

}

 

package cn.qlq.utils.weixin;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import com.thoughtworks.xstream.XStream;

import cn.qlq.bean.weixin.response.AbstractResponseMessage;
import cn.qlq.bean.weixin.response.TextResponseMessage;

public class MessageUtils {

    /**
     * xml數據轉map
     *
     * @param request
     * @return
     * @throws IOException
     * @throws DocumentException
     */
    public static Map<String, Object> xmlToMap(HttpServletRequest request) throws IOException, DocumentException {
        Map<String, Object> map = new HashMap<>();
        SAXReader reader = new SAXReader();

        InputStream inputStream = request.getInputStream();
        Document document = reader.read(inputStream);

        Element root = document.getRootElement();
        List<Element> list = root.elements();

        for (Element element : list) {
            map.put(element.getName(), element.getText());
        }

        inputStream.close();
        return map;
    }

    /**
     * 將文本消息對象轉換成xml
     *
     * @param message
     * @return
     */
    public static String messageToXml(AbstractResponseMessage message) {
        XStream xStream = new XStream();
        // 將xml的根元素替換成xml
        xStream.alias("xml", message.getClass());
        xStream.alias("item", cn.qlq.bean.weixin.response.NewsResponseMessageArticleItem.class);
        xStream.addImplicitArray(cn.qlq.bean.weixin.response.NewsResponseMessageArticle.class, "items");

        return xStream.toXML(message);
    }

    /**
     * 將文本消息對象轉換成xml
     *
     * @param message
     * @return
     */
    public static String messageToXml2(AbstractResponseMessage message) {
        XStream xStream = new XStream();
        // 將xml的根元素替換成xml
        xStream.alias("xml", message.getClass());
        return xStream.toXML(message);
    }

    /**
     * 訂閱後的歡迎信息
     * 
     * @return
     */
    public static String subscribeWelcomeText() {
        StringBuffer sb = new StringBuffer();
        sb.append("歡迎您的關注,這裏是喬治我的平臺:\n\n");
        sb.append("1.推薦一些優秀的文章\n");// \n表明換行
        sb.append("2.記錄一些美好時刻\n\n");

        return sb.toString();
    }

    /**
     * 生成文本消息
     * 
     * @param fromUserName
     * @param toUserName
     * @param content
     * @return
     */
    public static TextResponseMessage initTextMessage(String fromUserName, String toUserName, String content) {
        TextResponseMessage textResponseMessage = new TextResponseMessage();
        textResponseMessage.setFromUserName(fromUserName);
        textResponseMessage.setToUserName(toUserName);
        textResponseMessage.setMsgType(MessageHandler.MESSAGE_TEXT);
        textResponseMessage.setCreateTime(System.currentTimeMillis());
        textResponseMessage.setContent(content);

        return textResponseMessage;
    }

}

 結果以下:

 

5. 獲取用戶的精度緯度信息 

1.首先在接口設置開啓經緯度事件(開啓以後在用戶進入公衆號以後會主動拉取用戶的位置信息)

 

2. 處理上報地理位置事件

  用戶贊成上報地理位置後,每次進入公衆號會話時,都會在進入時上報地理位置,或在進入會話後每5秒上報一次地理位置,公衆號能夠在公衆平臺網站中修改以上設置。上報地理位置時,微信會將上報地理位置事件推送到開發者填寫的URL。 

推送的XML數據包格式以下:

<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[fromUser]]></FromUserName>
  <CreateTime>123456789</CreateTime>
  <MsgType><![CDATA[event]]></MsgType>
  <Event><![CDATA[LOCATION]]></Event>
  <Latitude>23.137466</Latitude>
  <Longitude>113.352425</Longitude>
  <Precision>119.385040</Precision>
</xml>

 

java處理:

封裝工具類:

package cn.qlq.bean.weixin;

/**
 * 上報地理位置事件
 * 
 * @author Administrator
 *
 */
public class LocationEventMessage extends EventMessage {

    /**
     * 地理位置緯度
     */
    private String Latitude;

    /**
     * 地理位置經度
     */
    private String Longitude;

    /**
     * 地理位置精度
     */
    private String Precision;

    public String getLatitude() {
        return Latitude;
    }

    public void setLatitude(String latitude) {
        Latitude = latitude;
    }

    public String getLongitude() {
        return Longitude;
    }

    public void setLongitude(String longitude) {
        Longitude = longitude;
    }

    public String getPrecision() {
        return Precision;
    }

    public void setPrecision(String precision) {
        Precision = precision;
    }

    @Override
    public String toString() {
        return "LocationEventMessage [Latitude=" + Latitude + ", Longitude=" + Longitude + ", Precision=" + Precision
                + ", ToUserName=" + ToUserName + ", FromUserName=" + FromUserName + ", CreateTime=" + CreateTime
                + ", MsgType=" + MsgType + ", Event=" + Event + "]";
    }

}

 

修改處理事件消息的代碼:(CLICK和VIEW是用於處理自定義菜單事件)

    /**
     * 處理事件消息(訂閱和取消訂閱)
     * 
     * @param messageMap
     * @return
     */
    private static AbstractResponseMessage handleEventMessage(Map<String, Object> messageMap) {
        EventMessage message = BeanUtils.map2Bean(messageMap, EventMessage.class, true);
        String event = message.getEvent();
        if (StringUtils.isNotBlank(event)) {
            System.out.println("您接收到事件消息, 事件類型爲: " + event);
        }

        // 關注的時候
        if (MESSAGE_SUBSCRIBE.equals(event)) {
            System.out.println("這裏能夠向數據庫插入數據");

            String responseMsg = MessageUtils.subscribeWelcomeText();
            return MessageUtils.initTextMessage(message.getToUserName(), message.getFromUserName(), responseMsg);
        }

        // 取消關注(不用回傳消息.須要將用戶產生的數據刪除)
        if (MESSAGE_SUBSCRIBE.equals(event)) {
            System.out.println("這時須要從數據刪除  " + message.getFromUserName() + " 用戶產生的相關數據");
            return null;
        }

        // 點擊自定義的點擊菜單事件
        if (MESSAGE_EVENT_CLICK.equals(event)) {
            ClickViewEventMessage map2Bean = BeanUtils.map2Bean(messageMap, ClickViewEventMessage.class, true);
            String eventKey = map2Bean.getEventKey();
            String content = "您點擊的按鈕的key爲: " + eventKey;
            return MessageUtils.initTextMessage(map2Bean.getToUserName(), map2Bean.getFromUserName(), content);
        }

        // VIEW菜單的事件
        if (MESSAGE_EVENT_VIEW.equals(event)) {
            ClickViewEventMessage map2Bean = BeanUtils.map2Bean(messageMap, ClickViewEventMessage.class, true);
            String eventKey = map2Bean.getEventKey();
            String content = "您點擊的按鈕跳轉的URL爲: " + eventKey;
            return MessageUtils.initTextMessage(map2Bean.getToUserName(), map2Bean.getFromUserName(), content);
        }

        // LOCATION菜單的事件
        if (MESSAGE_EVENT_LOCATION.equals(event)) {
            LocationEventMessage map2Bean = BeanUtils.map2Bean(messageMap, LocationEventMessage.class, true);
            String latitude = map2Bean.getLatitude();
            String longitude = map2Bean.getLongitude();
            String precision = map2Bean.getPrecision();
            String content = "您的經度:" + latitude + ", 您的維度:" + longitude + ", 您的精度: " + precision;
            return MessageUtils.initTextMessage(map2Bean.getToUserName(), map2Bean.getFromUserName(), content);
        }

        return null;
    }

測試結果:(須要開啓GPS才能夠觸發該事件消息發送)

 

總結:

(1)回傳xml數據格式的數據時,能夠用字符串進行拼接,不必定用XStream進行轉換

(2)微信回傳的消息支持換行,換行符爲\n

(3)圖文消息是最多見的消息,回傳消息的時候注意回傳的xml數據格式

 

完整的代碼接收消息與回傳消息代碼參考:https://github.com/qiao-zhi/springboot-ssm.git

相關文章
相關標籤/搜索