微信公众号开发详细笔记

目录大纲

    • 1、简介
    • 2、springBoot基本环境搭建
    • 3、公众号开发环境准备
      • 3.1了解公众号
      • 3.2 公众号开发流程
      • 3.3 内网穿透
      • 3.4 接口测试号申请
      • 3.5 配置及接入
    • 4、接收用户消息
    • 5 被动回复用户消息
      • 5.1 回复的消息实体
        • 5.1.1 `BaseMessage.java`
        • 5.1.2文本消息类:`TextMessage.java`
        • 5.1.3 图片消息:`ImageMessage.java`
        • 5.1.4 音乐消息:`MusicMessage.java`
        • 5.1.5 视频消息:`VideoMessage.java`
        • 5.1.6 语音消息:`VoiceMessage.java`
        • 5.1.7 图文消息:`NewsMessage.java`
      • 5.2 处理消息
        • 5.2.1在WxCallBackApi.java中增加如下回复的代码
        • 5.2.2 WxService中的responseMsg方法
        • 5.2.3 MsgType.java 枚举类

1、简介

各位读者,你们好!文章的内容是微信公众号开发学习笔记。是java版本的SpringBoot 项目。项目中基本包括了公众号开发的部分知识点。也参考着b站的视频代码敲了一顿。所有的代码都结合了自己的经验自己写的。在B站罗老师的的代码基础上做了改造。修改了一些bug。同时也应用到了一些设计模式。希望对广大读者提供一点帮助。

2、springBoot基本环境搭建

1、idea中创建springBoot项目
File->New Project后如下图:
微信公众号开发详细笔记_第1张图片
点击Next:填写项目名称等
微信公众号开发详细笔记_第2张图片
点击Finish,项目目录如下:
微信公众号开发详细笔记_第3张图片
创建 Application。名字为MainApplication。代码如下:

package com.boot.mp;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MainApplication {
    public static void main(String[] args) {
        SpringApplication.run(MainApplication.class,args);
    }
}

3、公众号开发环境准备

3.1了解公众号

公众号开发一般包括:订阅号和服务号
订阅号:为媒体和个人提供一种新的信息传播方式,主要功能是在微信侧给用户传达资讯;(功能类似报纸杂志,提供新闻信息或娱乐趣事)
适用人群:个人、媒体、企业、政府或其他组织。
群发次数:订阅号(认证用户、非认证用户)1天内可群发1条消息。
服务号:为企业和组织提供更强大的业务服务与用户管理能力,主要偏向服务类交互(功能类似12315,114,银行,提供绑定信息,服务交互的);
适用人群:媒体、企业、政府或其他组织。
群发次数:服务号1个月(按自然月)内可发送4条群发消息。

选择:
①如果想用公众平台简单发发消息,做宣传推广服务,建议可选择订阅号;
②如果想用公众号获得更多的功能,例如开通微信支付,建议可以选择服务号。

3.2 公众号开发流程

接入微信公众平台开发,开发者需要按照如下步骤完成:

1、填写服务器配置

2、验证服务器地址的有效性

3、依据接口文档实现业务逻辑
上述在官网已经描述:链接地址https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html

3.3 内网穿透

由于用户在使用公众号时,都要首先调用微信的服务器,再由微信的服务器,转发到我们自己的服务器上。所以在开发过程中需要开通内外网映射。有条件的话最好申请一个域名(可以不备案)。通过内网穿透工具将域名映射到自己的本地ip上。交互流程图如下:
微信公众号开发详细笔记_第4张图片

需要准备外网的环境地址,供微信服务器访问。
下载ngrok 官网地址:https://www.ngrok.cc/
注册、实名认证(需要收费)、购买隧道(有免费版本)
微信公众号开发详细笔记_第5张图片

开通隧道后,自动会为你生成一个域名。
微信公众号开发详细笔记_第6张图片
建立内外网映射:

下载客户端解压后,到sunny 所在的文件夹下 执行:
./sunny clientid 隧道id(隧道开通后即可生成一个id)
出现:
微信公众号开发详细笔记_第7张图片
成功后可进行访问测试。

3.4 接口测试号申请

由于个人申请的订阅号,需要申请认证后才能具备某些接口的权限。所以为了方便开发,我们需要申请接口测试号:申请地址:
https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login
申请成功后会提供一个公众测试号的appid。并提供了一个测试公众号。后续的
开发都可以依据此公众号进行开发。

3.5 配置及接入

微信官方提到:

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

第二步:验证消息的确来自微信服务器
开发者提交信息后,微信服务器将发送 GET 请求到填写的服务器地址 URL 上,

**注意:**上述步骤如果是正式环境下,可按上述方式进行配置。如果我们申请了接口测试号后,在申请测试号时填写地址即可,配置提交后,微信会自动调用我们填写的地址进行配置验证。处理方式如下:
(1)验证消息的确来自微信服务器
在接口测试号中进行填写url地址,和token 直接提交即可进行验证。提交后,可模拟微信服务器发消息到我们开发人员提供的接口。token 随便填。URL:填写 ngrok生成的域名和我们服务的URL进行拼接。
微信公众号开发详细笔记_第8张图片
在springboot项目中自定义接口 WxCallBackApi.java

package com.boot.mp.wx.controller;

import com.boot.mp.wx.service.WxService;
import com.boot.mp.wx.utils.ApplicationContextUtil;
import com.boot.mp.wx.utils.WxUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Map;

@RestController
@RequestMapping("/wxCall")
public class WxCallBackApi {

    @Autowired
    private WxService wxService;
   // @RequestMapping(value = "/cc",method = RequestMethod.GET)
    @GetMapping
    public String getMsg(HttpServletRequest httpServletRequest){
        System.out.println("Get");
        /**
         * signature	微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
         * timestamp	时间戳
         * nonce	随机数
         * echostr	随机字符串
         */
         String signature = httpServletRequest.getParameter("signature");
         String timestamp = httpServletRequest.getParameter("timestamp");
         String nonce = httpServletRequest.getParameter("nonce");
         String echostr = httpServletRequest.getParameter("echostr");
         //验证
         if(wxService.check(timestamp,nonce,signature)){
             System.out.println("接入成功");
             //返回echostr
             return echostr;
         }else {
             System.out.println("接入失败");
             return null;
         }
    }
}

WxService.java 中的check方法

package com.boot.mp.wx.service;

import com.alibaba.fastjson.JSONObject;
import com.boot.mp.wx.Constant;
import com.boot.mp.wx.dto.msg.*;
import com.boot.mp.wx.dto.token.AccessToken;
import com.boot.mp.wx.MsgType;
import com.boot.mp.wx.utils.HttpRequestUtil;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.core.util.QuickWriter;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;
import com.thoughtworks.xstream.io.xml.XppDriver;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.*;
import java.util.*;

/**
 * 微信服务
 */
@Service
public class WxService {
    public static final String TOKEN = "testToken";//在微信配置界面自定义的token

    // 缓存accessToken
    private static AccessToken accessToken;

    @Resource
    private RedisService redisService;

    /**
     * 校验是否是从微信服务发送过来的消息
     *
     * @param timestamp
     * @param nonce
     * @param signature
     * @return
     */
    public boolean check(String timestamp, String nonce, String signature) {
        //1.将token、timestamp、nonce三个参数进行字典序排序
        String[] arr = new String[]{TOKEN, timestamp, nonce};
        Arrays.sort(arr);
        //2.将三个参数字符串拼接成一个字符串进行sha1加密  https://www.cnblogs.com/2333/p/6405386.html
        String str = arr[0] + arr[1] + arr[2];
        str = DigestUtils.sha1Hex(str);//sha1加密, 直接用的commons-codec包的工具类
        System.out.println("str:" + str);
        //3.将加密后的字符串和signature比较
        System.out.println(signature);
        return str.equalsIgnoreCase(signature);
    }
}

罗老师的 加密算法

/**
     * 自定义加密算法
     */
    private static String sha1(String src) {
        StringBuilder stringBuilder = new StringBuilder();
        try {
            //获取一个加密对象
            MessageDigest messageDigest = MessageDigest.getInstance("sha1");
            byte[] digest = messageDigest.digest(src.getBytes(StandardCharsets.UTF_8));
            char[] chars = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

            //处理加密结果
            for (byte b : digest) {
                stringBuilder.append(chars[(b >> 4) & 15]);
                stringBuilder.append(chars[b & 15]);
            }

        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return stringBuilder.toString();
    }

4、接收用户消息

当普通微信用户向公众账号发消息时,微信服务器将 POST 消息的 XML 数据包到开发者填写的 URL 上。

消息类型包含多种消息:

  • 文本消息
  • 图片消息
  • 语音消息
  • 视频消息
  • 小视频消息
  • 地理位置消息
  • 链接消息

由于接收的消息是xml格式的数据,一般我们在项目开发时都需要将其转换为实体或map。这里转为map
接收消息并转换为map格式

在WxCallBackApi.java中添加 接收post请求的代码

 /**
     * 接收用户消息和事件推送
     * @return
     */
    @PostMapping
    @ResponseBody
    public String receiveMsg(HttpServletRequest request) throws IOException {
        //System.out.println("post");
        String str = WxUtils.getXMl(request.getInputStream());
        InputStream inputStream =  new ByteArrayInputStream(str.toString().getBytes(StandardCharsets.UTF_8));
        /**
         *
         * ToUserName	开发者微信号
         * FromUserName	发送方帐号(一个OpenID)
         * CreateTime	消息创建时间 (整型)
         * MsgType	消息类型,文本为text(消息可为任意类型的消息,如位置、语音、视频等)
         * Content	文本消息内容
         * MsgId	消息id,64位整型
         * 
         *      
         *      
         *      1650785398 单位为 秒
         *       消息类型
         *       消息内容
         *      23633561511377086
         * 
         */
        //System.out.println(str.toString());
        //接受消息转为map类型
        Map<String,String> map = WxUtils.parseRequest(inputStream);
        System.out.println(map);
        // 接受到消息进行自动消息回复。微信消息的回复是以xml形式进行回复的。每种类型的消息都对应的不同的xml。
        // 这里通过对象的方式将消息体转换为xml格式转发给微信服务器
        String message = wxService.responseMsg(map);
        WxService wxService = (WxService) ApplicationContextUtil.getBean(WxService.class);

        System.out.println(message);
        return message;
    }

WxUtils.java

package com.boot.mp.wx.utils;

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

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * 微信公众号开发通用工具类
 *
 */
public class WxUtils {


    /**
     * stream转string
     * @param is
     * @return
     */
    public static String convertStreamToString(InputStream is) {
        BufferedReader reader = new BufferedReader(new InputStreamReader(is));
        StringBuilder sb = new StringBuilder();
        String line = null;

        try {
            while ((line = reader.readLine()) != null) {
                sb.append(line + "/n");
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return sb.toString();
    }

    /**
     * 递归遍历当前节点下的所有节点
     */
    public void listNodes(Element node) {
        System.out.println("当前节点的名称:" + node.getName());
        //首先获取当前节点的所有属性节点
        List<Element> list = node.elements();
        //遍历属性节点
        for (Element element : list) {
            System.out.println("属性" + element.getName() + ":" + element.getStringValue());
        }
        //如果当前节点内容不为空,则输出
        if (!(node.getTextTrim().equals(""))) {
            System.out.println(node.getName() + ":" + node.getText());
        }
        //同时迭代当前节点下面的所有子节点
        //使用递归
        Iterator<Element> iterator = node.elementIterator();
        while (iterator.hasNext()) {
            Element e = iterator.next();
            listNodes(e);
        }
    }


    /**
     * stream转string
     *
     * @param is
     * @return
     * @throws IOException
     */
    public static String inputStream2String(InputStream is) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        int i = -1;
        while ((i = is.read()) != -1) {
            baos.write(i);
        }
        return baos.toString();
    }

    public static String getXMl(InputStream inputStream) throws IOException {
        byte[] b = new byte[1024];
        int len;
        StringBuilder stringBuilder = new StringBuilder();
        while ((len= inputStream.read(b))!=-1){
            stringBuilder.append("");
            stringBuilder.append(new String(b,0,len));
        }
        inputStream.close();
        return stringBuilder.toString();
    }

    /**
     * 自定义加密算法
     */
    private static String sha1(String src) {
        StringBuilder stringBuilder = new StringBuilder();
        try {
            //获取一个加密对象
            MessageDigest messageDigest = MessageDigest.getInstance("sha1");
            byte[] digest = messageDigest.digest(src.getBytes(StandardCharsets.UTF_8));
            char[] chars = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

            //处理加密结果
            for (byte b : digest) {
                stringBuilder.append(chars[(b >> 4) & 15]);
                stringBuilder.append(chars[b & 15]);
            }

        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return stringBuilder.toString();
    }

    /**
     * 解析xml 数据包
     * @param is
     * @return
     */
    public static Map<String, String> parseRequest(InputStream is) throws IOException {
        Map<String, String> map = new HashMap<>();
        SAXReader saxReader = new SAXReader();
        try {
            //读取Resource 下的xml文件(此时添加了xml头部,可以正常解析)https://www.jb51.net/article/216504.htm
//            InputStream inputStream = WxService.class.getResourceAsStream("/xml/test.xml");
            //读入输入流,获取文档对象(添加了xml头部,才可以正常解析)不加xml头 则报错(文件提前结束)
            Document document = saxReader.read(is);
            assert document != null;
            Element root = document.getRootElement();
            //通过根结点获取所有的子节点对象
            List<Element> elements = root.elements();
            //将子节点放入map中
            for (Element element : elements) {
                map.put(element.getName(), element.getStringValue());
            }
        } catch (DocumentException e) {
            e.printStackTrace();
        }
        //根据文档对象获取根结点
        is.close();
        return map;
    }

    /**
     *
     */
}

5 被动回复用户消息

当用户发送消息给公众号时(或某些特定的用户操作引发的事件推送时),会产生一个 POST 请求,开发者可以在响应包(Get)中返回特定 XML 结构,来对该消息进行响应(现支持回复文本、图片、图文、语音、视频、音乐)。严格来说,发送被动响应消息其实并不是一种接口,而是对微信服务器发过来消息的一次回复。

微信服务器在将用户的消息发给公众号的开发者服务器地址(开发者中心处配置)后,微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次,如果在调试中,发现用户无法收到响应的消息,可以检查是否消息处理超时。关于重试的消息排重,有 msgid 的消息推荐使用 msgid 排重。事件类型消息推荐使用FromUserName + CreateTime 排重。

如果开发者希望增强安全性,可以在开发者中心处开启消息加密,这样,用户发给公众号的消息以及公众号被动回复用户消息都会继续加密,详见被动回复消息加解密说明。

假如服务器无法保证在五秒内处理并回复,必须做出下述回复,这样微信服务器才不会对此作任何处理,并且不会发起重试(这种情况下,可以使用客服消息接口进行异步回复),否则,将出现严重的错误提示。详见下面说明:

1、直接回复success(推荐方式) 2、直接回复空串(指字节长度为0的空字符串,而不是 XML 结构体中 content 字段的内容为空)

一旦遇到以下情况,微信都会在公众号会话中,向用户下发系统提示“该公众号暂时无法提供服务,请稍后再试”:

1、开发者在5秒内未回复任何内容 2、开发者回复了异常数据,比如 JSON 数据等

另外,请注意,回复图片(不支持 gif 动图)等多媒体消息时需要预先通过素材管理接口上传临时素材到微信服务器,可以使用素材管理中的临时素材,也可以使用永久素材。

各消息类型需要的 XML 数据包结构如下:

目录

1 回复文本消息

2 回复图片消息

3 回复语音消息

4 回复视频消息

5 回复音乐消息

6 回复图文消息

5.1 回复的消息实体

回复消息时需要将实体映射为xml。这里先建回复消息类型的实体:

5.1.1 BaseMessage.java

package com.boot.mp.wx.dto.msg;

import com.thoughtworks.xstream.annotations.XStreamAlias;

import java.util.Map;

/**
 * 消息体父类,每种消息类型的公共属性
 */

public class BaseMessage {

    @XStreamAlias("ToUserName")
    private String toUserName;
    @XStreamAlias("FromUserName")
    private String fromUserName;
    @XStreamAlias("CreateTime")
    private String createTime;
    private String MsgType;

    public String getToUserName() {
        return toUserName;
    }

    public void setToUserName(String toUserName) {
        this.toUserName = toUserName;
    }

    public String getFromUserName() {
        return fromUserName;
    }

    public void setFromUserName(String fromUserName) {
        this.fromUserName = fromUserName;
    }

    public String getCreateTime() {
        return createTime;
    }

    public void setCreateTime(String createTime) {
        this.createTime = createTime;
    }

    public String getMsgType() {
        return MsgType;
    }

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

    public BaseMessage(Map<String, String> resultMap) {
        this.toUserName = resultMap.get("FromUserName");
        this.fromUserName = resultMap.get("ToUserName");
        this.createTime = System.currentTimeMillis() / 1000 + "";
    }
}

5.1.2文本消息类:TextMessage.java

package com.boot.mp.wx.dto.msg;

import com.thoughtworks.xstream.annotations.XStreamAlias;

import java.util.Map;

@XStreamAlias("xml")
public class TextMessage extends BaseMessage {

    @XStreamAlias("Content")
    private String content;

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    //构造方法
    public TextMessage(Map<String, String> resultMap, String content) {
        super(resultMap);
        this.setContent(content);
        this.setMsgType("text");
    }
}

5.1.3 图片消息:ImageMessage.java

package com.boot.mp.wx.dto.msg;

import com.thoughtworks.xstream.annotations.XStreamAlias;

import java.util.Map;

/**
 * 定义图片类型实体
 */

@XStreamAlias("xml")
public class ImageMessage extends BaseMessage {

    @XStreamAlias("Image")
    private Image image;

    public Image getImage() {
        return image;
    }

    public void setImage(Image image) {
        this.image = image;
    }

    public ImageMessage(Map<String, String> resultMap, Image image) {
        super(resultMap);
        this.setMsgType("image");
        this.image = image;

    }
}

Image.java

package com.boot.mp.wx.dto.msg;

import com.thoughtworks.xstream.annotations.XStreamAlias;

@XStreamAlias("Image")
public class Image {

    @XStreamAlias("MediaId")
    private String mediaId;
    public String getMediaId() {
        return mediaId;
    }

    public void setMediaId(String mediaId) {
        this.mediaId = mediaId;
    }

    public Image(String mediaId) {
        super();
        this.mediaId = mediaId;
    }
}

5.1.4 音乐消息:MusicMessage.java

package com.boot.mp.wx.dto.msg;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import java.util.Map;
/**
 * 音乐消息类型的dto
 */
@XStreamAlias("xml")
public class MusicMessage extends BaseMessage {

    @XStreamAlias("Music")
    private Music music;

    public Music getMusic() {
        return music;
    }

    public void setMusic(Music music) {
        this.music = music;
    }

    public MusicMessage(Map<String, String> resultMap, Music music) {
        super(resultMap);
        this.music = music;
        this.setMsgType("music");
    }
}

Music.java

package com.boot.mp.wx.dto.msg;
import com.thoughtworks.xstream.annotations.XStreamAlias;
@XStreamAlias("Music")
public class Music {
    @XStreamAlias("Title")
    private String title;
    @XStreamAlias("Description")
    private String description;
    @XStreamAlias("MusicUrl")
    private String musicUrl;
    @XStreamAlias("HQMusicUrl")
    private String hQMusicUrl;
    @XStreamAlias("ThumbMediaId")
    private String thumbMediaId;

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public String getMusicUrl() {
        return musicUrl;
    }

    public void setMusicUrl(String musicUrl) {
        this.musicUrl = musicUrl;
    }

    public String gethQMusicUrl() {
        return hQMusicUrl;
    }

    public void sethQMusicUrl(String hQMusicUrl) {
        this.hQMusicUrl = hQMusicUrl;
    }

    public String getThumbMediaId() {
        return thumbMediaId;
    }

    public void setThumbMediaId(String thumbMediaId) {
        this.thumbMediaId = thumbMediaId;
    }


    public Music(String title, String description, String musicUrl, String hQMusicUrl, String thumbMediaId) {
        this.title = title;
        this.description = description;
        this.musicUrl = musicUrl;
        this.hQMusicUrl = hQMusicUrl;
        this.thumbMediaId = thumbMediaId;
    }
}

5.1.5 视频消息:VideoMessage.java

package com.boot.mp.wx.dto.msg;

import com.thoughtworks.xstream.annotations.XStreamAlias;

import java.util.Map;

@XStreamAlias("xml")
public class VideoMessage extends BaseMessage {

    @XStreamAlias("Video")
    private Video video;

    public Video getVideo() {
        return video;
    }

    public void setVideo(Video video) {
        this.video = video;
    }

    public VideoMessage(Map<String, String> resultMap, Video video) {
        super(resultMap);
        this.video = video;
        this.setMsgType("video");
    }
}

Video.java

package com.boot.mp.wx.dto.msg;

import com.thoughtworks.xstream.annotations.XStreamAlias;

@XStreamAlias("Video")
public class Video {

    @XStreamAlias("MediaId")
    private String mediaId;
    @XStreamAlias("Title")
    private String title;
    @XStreamAlias("Description")
    private String description;

    public String getMediaId() {
        return mediaId;
    }

    public void setMediaId(String mediaId) {
        this.mediaId = mediaId;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public Video(String mediaId, String title, String description) {
        this.mediaId = mediaId;
        this.title = title;
        this.description = description;
    }
}

5.1.6 语音消息:VoiceMessage.java

package com.boot.mp.wx.dto.msg;

import com.thoughtworks.xstream.annotations.XStreamAlias;

import java.util.Map;

/**
 *  定义语音消息类型dto
 */
@XStreamAlias("xml")
public class VoiceMessage extends BaseMessage {

    @XStreamAlias("Voice")
    private Voice voice;

    public Voice getVoice() {
        return voice;
    }

    public void setVoice(Voice voice) {
        this.voice = voice;
    }

    public VoiceMessage(Map<String, String> resultMap, Voice voice) {
        super(resultMap);
        this.setMsgType("voice");
        this.voice = voice;
    }
}

Voice.java

package com.boot.mp.wx.dto.msg;

import com.thoughtworks.xstream.annotations.XStreamAlias;

@XStreamAlias("Voice")
public class Voice {

    @XStreamAlias("MediaId")
    private String mediaId;

    public Voice(String mediaId) {
        this.mediaId = mediaId;
    }

    public String getMediaId() {
        return mediaId;
    }

    public void setMediaId(String mediaId) {
        this.mediaId = mediaId;
    }
}

5.1.7 图文消息:NewsMessage.java

package com.boot.mp.wx.dto.msg;

import com.thoughtworks.xstream.annotations.XStreamAlias;

import java.util.List;
import java.util.Map;

/**
 * 图文消息dto
 */
@XStreamAlias("xml")
public class NewsMessage extends BaseMessage {

    @XStreamAlias("ArticleCount")
    private String articleCount;

    @XStreamAlias("Articles")
    private List<Article> articleList;

    public String getArticleCount() {
        return articleCount;
    }

    public void setArticleCount(String articleCount) {
        this.articleCount = articleCount;
    }

    public List<Article> getArticleList() {
        return articleList;
    }

    public void setArticleList(List<Article> articleList) {
        this.articleList = articleList;
    }

    public NewsMessage(Map<String, String> resultMap, List<Article> articleList) {
        super(resultMap);
        this.setArticleCount(articleList.size()+"");
        this.articleList = articleList;
        this.setMsgType("news");
    }
}

Article.java

package com.boot.mp.wx.dto.msg;

import com.thoughtworks.xstream.annotations.XStreamAlias;

/**
 * 图文消息体,单个文章
 */
@XStreamAlias("item")//映射到xml中的item这个节点
public class Article {

    //图文消息标题
    @XStreamAlias("Title")
    private String title;

    //图文消息描述
    @XStreamAlias("Description")
    private String description;

    //图片链接
    @XStreamAlias("PicUrl")
    private String picUrl;

    //点击图文消息跳转链接
    @XStreamAlias("Url")
    private String url;

    public Article(String title, String description, String picUrl, String url) {
        this.title = title;
        this.description = description;
        this.picUrl = picUrl;
        this.url = url;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public String getPicUrl() {
        return picUrl;
    }

    public void setPicUrl(String picUrl) {
        this.picUrl = picUrl;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }
}

5.2 处理消息

处理消息就是将将消息内容回复给用户。回复的形式是xml。我们在项目开发过程中也会自定义实体的消息回复给用户。在回复消息时,并没有根据罗老师的判断方式进行回复。我这里使用的策略模式:代码如下:

5.2.1在WxCallBackApi.java中增加如下回复的代码

  //System.out.println(str.toString());
        //接受消息转为map类型
        Map<String,String> map = WxUtils.parseRequest(inputStream);
        System.out.println(map);
        // 接受到消息进行自动消息回复。微信消息的回复是以xml形式进行回复的。每种类型的消息都对应的不同的xml。
        // 这里通过对象的方式将消息体转换为xml格式转发给微信服务器
        String message = wxService.responseMsg(map);
        WxService wxService = (WxService) ApplicationContextUtil.getBean(WxService.class);

        System.out.println(message);
        return message;

5.2.2 WxService中的responseMsg方法

/**
     * 消息回复
     *
     * @param map
     */
    public String responseMsg(Map<String, String> map) {
        //获取消息类型,根据消息类型判断调用哪个实体。这里使用策略模式进行封装
        //这里需要根据业务需要自行封装。如果文字中包含图文就是回复给用户 图文消息。
        String msgType = map.get("Content").contains("图文") ? "news" : map.get("MsgType");
        BaseMessage baseMessage = null;

        //获取枚举值
        MsgType mt = MsgType.getEnumMsgType(msgType);
        if (mt != null) {
            baseMessage = mt.getMessageDeal().dealMessage(map);
        }
        //将bean对象转换为xml
        return beanToXml(baseMessage);
    }

5.2.3 MsgType.java 枚举类

package com.boot.mp.wx;

import com.boot.mp.wx.service.WxMessageDeal;
import com.boot.mp.wx.service.impl.*;

/**
 * 定义枚举类型的消息,当调用getValues时会自动调用枚举实例,并调用构造方法。
 * 扩展:用户可以将一个枚举类型看作是一个类,它继承于java.lang.Enum类,当定义一个枚举类型时,
 *      每一个枚举类型成员都可以看作是枚举类型的一个实例,这些枚举类型成员默认都被final、public、static所修饰,
 *      所以当使用枚举类型成员时直接使用枚举类型名称调用枚举类型成员即可。
 */
public enum MsgType {
    //文本消息
    TEXT_MSG("text",new TextMessageDeal()),
    //图片消息
    IMAGE_MSG("image",new ImageMessageDeal()),
    //语音消息
    VOICE_MSG("voice",new VoiceMessageDeal()),
    //视频消息
    VIDEO_MSG("music",new VideoMessageDeal()),
    //音乐消息
    MUSIC_MSG("music",new MusicMessageDeal()),
    //图文消息
    NEWS_MSG("news",new NewsMessageDeal());

    //定义消息类型
    private final String msgType;
    //一个接口引用
    private final WxMessageDeal messageDeal;

    //构造方法
    MsgType(String t, WxMessageDeal messageDeal) {
        this.msgType = t;
        this.messageDeal=messageDeal;
    }
    // 获取消息处理方式
    public WxMessageDeal getMessageDeal(){
        return this.messageDeal ;
    }

    //根据类型获取消息类型枚举信息
    public static MsgType getEnumMsgType(String msgType){
        for (MsgType m : MsgType.values()) {
            if (m.msgType.equals(msgType)) {
                return m;
            }
        }
        return null;
    }
}

待更新。。。。。

你可能感兴趣的:(微信公众号,微信,java,spring,boot)