开始

本文是 微信公众号开发者模式介绍及接入 的后续,如没看过前文的话,可能看本文会有些懵逼。本文主要介绍微信公众平台的素材、消息管理接口的开发。由于个人的订阅号是没有大多数接口的权限的,所以我们需要使用微信官方提供的测试号来进行开发。测试号的申请可参考下文:

使用微信测试账号对网页进行授权
图文消息

本小节我们来开发回复图文消息的功能,官方文档地址如下:

https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140543

回复图文消息所需传递的参数如下:

注:多图文消息不会显示Description参数的信息

官方的图文消息示例数据结构如下:

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

图文消息都在Articles标签内,而每个item标签都包含一条图文消息,有多少个item标签就代表有多少条图文消息。

在开发回复图文消息的时候,我们需要使用到一张图片来作为图文消息的封面,找一个图片文件放在工程的resources/static目录下即可,并确保能够在外网上访问:

看完了官方的示例数据及文档,那么我们就来开发一下图文消息的回复吧。首先是创建一个基类,封装通用的字段,代码如下:

package org.zero01.weixin.mqdemo.vo;import com.thoughtworks.xstream.annotations.XStreamAlias;import lombok.Getter;import lombok.Setter;/** * @program: mq-demo * @description: 图文消息基类 * @author: 01 * @create: 2018-07-02 20:24 **/@Getter@Setterpublic class BaseMassage { /** * 接收方账号 */ @XStreamAlias("ToUserName") private String toUserName; /** * 发送方账号 */ @XStreamAlias("FromUserName") private String fromUserName; /** * 消息创建时间 (整型) */ @XStreamAlias("CreateTime") private long createTime; /** * 消息类型 */ @XStreamAlias("MsgType") private String msgType;}

然后是具体的封装每条图文消息字段的对象,代码如下:

package org.zero01.weixin.mqdemo.vo;import com.thoughtworks.xstream.annotations.XStreamAlias;import lombok.Getter;import lombok.Setter;/** * @program: mq-demo * @description: 图文消息对象 * @author: 01 * @create: 2018-07-02 20:19 **/@Getter@Setterpublic class NewsItem{ @XStreamAlias("Title") private String title; @XStreamAlias("Description") private String description; @XStreamAlias("PicUrl") private String picUrl; @XStreamAlias("Url") private String url;}

接着是包含每条图文消息的容器对象,代码如下:

package org.zero01.weixin.mqdemo.vo;import com.thoughtworks.xstream.annotations.XStreamAlias;import lombok.Getter;import lombok.Setter;import java.util.List;/** * @program: mq-demo * @description: 图文消息容器对象 * @author: 01 * @create: 2018-07-02 20:29 **/@Getter@Setterpublic class NewsMessage extends BaseMassage{ @XStreamAlias("ArticleCount") private int articleCount; @XStreamAlias("Articles") private List<NewsItem> articles;}

将图文消息结构都封装成一个个的类后,就是需要组装图文消息以及将组装好的图文消息转换成xml格式的数据,发送给微信服务器了。所以我们需要在MessageUtil类中,新增如下两个方法:

/** * 图文消息转换为xml * * @param newsMessage * @return */public static String newsMessageToXml(NewsMessage newsMessage) { XStream xStream = new XStream(); xStream.processAnnotations(new Class[]{NewsItem.class, NewsMessage.class}); xStream.alias("xml", newsMessage.getClass()); xStream.alias("item", NewsItem.class); return xStream.toXML(newsMessage);}/** * 组装图文消息 * * @param toUserName * @param fromUserName * @return */public static String initNewsMessage(String toUserName, String fromUserName) { List<NewsItem> newsItemList = new ArrayList<>(); NewsMessage newsMessage = new NewsMessage(); NewsItem newsItem = new NewsItem(); newsItem.setTitle("图文消息"); newsItem.setDescription("这是一个图文消息"); newsItem.setPicUrl("http://zero.mynatapp.cc/code.jpg"); newsItem.setUrl("www.baidu.com"); newsItemList.add(newsItem); newsMessage.setToUserName(fromUserName); newsMessage.setFromUserName(toUserName); newsMessage.setCreateTime(System.currentTimeMillis()); newsMessage.setMsgType(MessageTypeEnum.MSG_NEWS.getMsgType()); newsMessage.setArticles(newsItemList); newsMessage.setArticleCount(newsItemList.size()); return newsMessageToXml(newsMessage);}

最后修改WeChatMqController中的text方法,增加一条判断,判断当用户输入数字1时,则回复图文消息。代码如下:

@PostMapping("/common")public String text(@RequestBody String xmlStr) { // 将xml格式的数据,转换为 AllMessage 对象 AllMessage allMessage = MessageUtil.xmlToAllMessage(xmlStr); // 是否是文本消息类型 if (allMessage.getMsgType().equals(MessageTypeEnum.MSG_TEXT.getMsgType())) { // 用户输入数字1时,回复图文消息 if ("1".equals(allMessage.getContent())) { return MessageUtil.initNewsMessage(allMessage.getToUserName(), allMessage.getFromUserName()); } // 自动回复用户所发送的文本消息 return MessageUtil.autoReply(allMessage, ContentEnum.CONTENT_PREFIX.getContent() + allMessage.getContent()); } // 是否是事件推送类型 else if (allMessage.getMsgType().equals(MessageTypeEnum.MSG_EVENT.getMsgType())) { // 是否为订阅事件 if (EventType.EVENT_SUBSCRIBE.getEventType().equals(allMessage.getEvent())) { // 自动回复欢迎语 return MessageUtil.autoReply(allMessage, ContentEnum.CONTENT_SUBSCRIBE.getContent()); } } else { // 暂不支持文本以外的消息回复 return MessageUtil.autoReply(allMessage, ContentEnum.CONTENT_NONSUPPORT.getContent()); } return MessageUtil.autoReply(allMessage, ContentEnum.CONTENT_NONSUPPORT.getContent());}

完成以上代码的编写后,启动SpringBoot,打开微信公众号,测试结果如下:


access_token的获取

本小节我们来看看如何获取access_token,官方文档地址如下:

https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140183

access_token是什么?官方的定义如下:

access_token是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_token。开发者需要进行妥善保存。access_token的存储至少要保留512个字符空间。access_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的access_token失效。

调用接口获取access_token需要传递的参数说明如下:

获取access_token成功后,接口所返回的参数说明如下:

从文档中我们可以看到,调用接口获取access_token时需要传递appid和secret,appid和secret可以在公众号的基本配置页面中获取,如下:

然后我们还需要安装提示,设置一下白名单的ip,即你机器的ip,不然是无法调用接口获取access_token的,如下:

将appid、secret以及获取access_token的接口url,配置到SpringBoot的配置文件中,如下:

wechat: mpAppid: wx8ed1xxxxxx9513dd mpAppSecret: 0c1b5b7ea5xxxxxxxxx14cb5b61258 accessTokenUrl: https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET

在工程中新建一个config包,在该包下新建一个 WeXinConfig 配置类,用于加载配置文件中所配置的appid和secret:

package org.zero01.weixin.mqdemo.config;import lombok.Data;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.context.annotation.Configuration;/** * @program: mq-demo * @description: 微信公众号配置类 * @author: 01 * @create: 2018-07-03 20:50 **/@Data@Configuration@ConfigurationProperties(prefix = "wechat")public class WeXinConfig { private String mpAppid; private String mpAppSecret; private String accessTokenUrl;}

因为我们需要序列化json数据以及发送http请求给微信服务器,所以需要使用到一些工具包,在maven的pom.xml文件中,加入如下依赖:

<!-- https://mvnrepository.com/artifact/org.json/json --><dependency> <groupId>org.json</groupId> <artifactId>json</artifactId> <version>20160810</version></dependency><!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient --><dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.5</version></dependency>

在util包下新建一个 WeiXinUtil 工具类,在该类中封装get、post请求方法,以及获取access_token的方法。代码如下:

package org.zero01.weixin.mqdemo.util;import org.apache.http.HttpResponse;import org.apache.http.client.methods.HttpGet;import org.apache.http.client.methods.HttpPost;import org.apache.http.entity.StringEntity;import org.apache.http.impl.client.CloseableHttpClient;import org.apache.http.impl.client.HttpClientBuilder;import org.apache.http.util.EntityUtils;import org.json.JSONObject;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import org.zero01.weixin.mqdemo.config.WeXinConfig;import org.zero01.weixin.mqdemo.vo.AccessToken;import java.io.IOException;/** * @program: mq-demo * @description: * @author: 01 * @create: 2018-07-03 21:04 **/@Componentpublic class WeiXinUtil { private static WeXinConfig wxConfig; public WeXinConfig getWeXinConfig() { return wxConfig; } @Autowired public void setWeXinConfig(WeXinConfig wxConfig) { WeiXinUtil.wxConfig = wxConfig; } /** * get请求 * * @param url * @return */ public static JSONObject doGet(String url) throws IOException { CloseableHttpClient client = HttpClientBuilder.create().build(); HttpGet httpGet = new HttpGet(url); HttpResponse response = client.execute(httpGet); String result = EntityUtils.toString(response.getEntity(), "utf-8"); return new JSONObject(result); } /** * post请求 * * @param url * @param outStr * @return */ public static JSONObject doPost(String url, String outStr) throws IOException { CloseableHttpClient client = HttpClientBuilder.create().build(); HttpPost httpPost = new HttpPost(url); httpPost.setEntity(new StringEntity(outStr, "utf-8")); HttpResponse response = client.execute(httpPost); String result = EntityUtils.toString(response.getEntity(), "utf-8"); return new JSONObject(result); } /** * 获取access_token * * @return * @throws IOException */ public static AccessToken getAccessToken() throws IOException { AccessToken token = new AccessToken(); // 替换appid和secret String url = wxConfig.getAccessTokenUrl() .replace("APPID", wxConfig.getMpAppid()) .replace("APPSECRET", wxConfig.getMpAppSecret()); JSONObject jsonObject = doGet(url); token.setToken(jsonObject.getString("access_token")); token.setExpiresIn(jsonObject.getInt("expires_in")); return token; }}

完成以上代码的编写后,新建一个测试类,测试一下是否能正常获取到access_token。测试代码如下:

package org.zero01.weixin.mqdemo.util;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.context.junit4.SpringRunner;import org.zero01.weixin.mqdemo.vo.AccessToken;import java.io.IOException;import static org.junit.Assert.*;@RunWith(SpringRunner.class)@SpringBootTestpublic class WeiXinUtilTest { @Test public void getAccessToken() throws IOException { AccessToken accessToken = WeiXinUtil.getAccessToken(); System.out.println("access_token: " + accessToken.getToken()); System.out.println("有效时间: " + accessToken.getExpiresIn()); }}

运行以上测试用例后,控制台输出如下:

access_token: 11_AMxhxO9soXndEc6XI-0hG0CWQ_oVQjaiPol6P2eMDLrSYpIrbiNMjHEDFwoOiKwG-ckgwPTHCiWypzK_reZohT7H5UdEYUmdlU_qq-oGQefv9q9A4mEkFV5WyiEFK5q5SsvsLR5QIKcjf1BhLDEfAIAAST有效时间: 7200

从测试结果中,可以看到,成功获取到了access_token,并且这个access_token的有效期是7200秒,也就是两个小时,和官方文档描述的一致。

一般在实际的项目开发中,我们都会把这个access_token缓存起来,缓存到本地或者nosql数据库中,然后每隔1.5个小时或2个小时的时候,就重新获取一次access_token,刷新缓存。这样做是为了避免在每个逻辑点都去重新获取access_token,因为这样会导致服务的不稳定,而且微信也规定了获取access_token的接口每天只能调用2000次,如果每个逻辑点都去重新获取access_token的话,不仅会导致服务不稳定,还容易把调用次数给花完。如下:


图片消息回复

本小节我们来看看如何进行图片消息的回复,官方文档地址如下:

https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140543

回复图片消息所需传递的参数如下:

官方的图文消息示例数据结构如下:

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

从所需传递的参数列表中可以看到,回复图片消息时需要传递一个MediaId,这是通过素材管理中的接口上传多媒体文件,得到的id。所以在开发回复图片消息的接口前,我们还需要开发一个上传多媒体文件的接口,以此来获得MediaId。关于素材管理接口的官方文档地址如下:

https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1444738726

新增临时素材接口调用说明如下:

上传素材成功后,返回的参数如下:

有一点要说明的是,个人的订阅号是没有素材管理接口的权限的,所以我们需要将之前配置的appid和AppSecret配置为测试号的,不然接口会调用失败,如果是已认证的服务号就可以直接使用。

由于需要上传图片素材才能发送图片消息,所以首先需要在 WexinUtil 中,新增一个 upload 方法,用于上传临时图片素材并返回素材的media_id。

但是在写代码前,需要先将上传临时素材的接口url地址配置到SpringBoot的配置文件中,如下:

wechat: ... uploadUrl: https://api.weixin.qq.com/cgi-bin/media/upload?access_token=ACCESS_TOKEN&type=TYPE

然后在配置类里加上这个配置的字段,如下:

...public class WeXinConfig { ... private String uploadUrl;}

upload 方法代码如下:

/** * 上传临时素材 * * @param filePath 需要上传的文件所在路径 * @param accessToken access_token * @param type 素材类型 * @return media_id * @throws IOException */public static String upload(String filePath, String accessToken, String type, String key) throws IOException { File file = new File(filePath); if (!(file.exists() || file.isFile())) { throw new IOException("文件不存在"); } String url = wxConfig.getUploadUrl() .replace("ACCESS_TOKEN", accessToken) .replace("TYPE", type); URL urlObj = new URL(url); // 打开连接 HttpURLConnection connection = (HttpURLConnection) urlObj.openConnection(); // 设置属性 connection.setRequestMethod("POST"); connection.setDoInput(true); connection.setDoOutput(true); connection.setUseCaches(false); // 设置头信息 connection.setRequestProperty("Connection", "Keep-Alive"); connection.setRequestProperty("Charset", "UTF-8"); // 设置边界 String boundary = "----------" + System.currentTimeMillis(); connection.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary); StringBuilder sb = new StringBuilder(); sb.append("--").append(boundary).append("\r\n") .append("Content-Disposition;form-data;name=\"file\";filename=\"") .append(file.getName()) .append("\"\r\n") .append("Content-Type:application/octet-stream\r\n\r\n"); byte[] head = sb.toString().getBytes("UTF-8"); // 获得输出流 OutputStream output = new DataOutputStream(connection.getOutputStream()); // 输出表头 output.write(head); // 文件正文部分 // 把文件以流文件的方式,推入到url中 DataInputStream input = new DataInputStream(new FileInputStream(file)); int bytes = 0; byte[] bufferOutput = new byte[1024]; while ((bytes = input.read(bufferOutput)) != -1) { output.write(bufferOutput, 0, bytes); } input.close(); // 结尾部分,定义最后数据分割线 byte[] foot = ("\r\n--" + boundary + "--\r\n").getBytes("utf-8"); output.write(foot); output.flush(); output.close(); StringBuilder buffer = new StringBuilder(); String result = null; try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) { String line = null; while ((line = reader.readLine()) != null) { buffer.append(line); } result = buffer.toString(); } catch (IOException e) { e.printStackTrace(); } JSONObject jsonObject = new JSONObject(result); log.info("response data: {}", jsonObject); return jsonObject.getString(key);}

在测试类中新增一个测试方法,测试代码如下:

@Testpublic void upload() throws IOException { String filePath = "Z:/v2-9b17df91629f842edd472d7cfcaa9c4b_hd.jpg"; AccessToken accessToken = WeiXinUtil.getAccessToken(); String mediaId = WeiXinUtil.upload(filePath, accessToken.getToken(), "image"); System.out.println(mediaId);}

控制台输出结果如下:

mediaId: 5_PCrofX1_KIpSfWzJE-tu7AxQjxw6zlQ44oBuUkM_PZ6FiPeDY0a7vcWU2zdap9

获取到media_id后,就可以开始开发回复图片消息功能了,首先根据官方给出的数据结构,封装好各个实体类。Image类代码如下:

package org.zero01.weixin.mqdemo.vo;import com.thoughtworks.xstream.annotations.XStreamAlias;import lombok.Data;@Datapublic class Image { @XStreamAlias("MediaId") private String mediaId;}

ImageMessage类代码如下:

package org.zero01.weixin.mqdemo.vo;import com.thoughtworks.xstream.annotations.XStreamAlias;import lombok.Data;@Datapublic class ImageMessage extends BaseMassage { @XStreamAlias("Image") private Image image;}

然后在MessageUtil中新增如下两个方法:

/** * 将图片消息转换为xml * * @param imageMessage * @return */public static String imageMessageToXml(ImageMessage imageMessage) { XStream xStream = new XStream(); xStream.processAnnotations(new Class[]{ImageMessage.class, Image.class}); xStream.alias("xml", imageMessage.getClass()); return xStream.toXML(imageMessage);}/** * 组装图片消息对象 * * @param toUserName * @param fromUserName * @return */public static String initImageMessage(String toUserName, String fromUserName) { Image image = new Image(); image.setMediaId("5_PCrofX1_KIpSfWzJE-tu7AxQjxw6zlQ44oBuUkM_PZ6FiPeDY0a7vcWU2zdap9"); ImageMessage imageMessage = new ImageMessage(); imageMessage.setFromUserName(toUserName); imageMessage.setToUserName(fromUserName); imageMessage.setMsgType(MessageTypeEnum.MSG_IMAGE.getMsgType()); imageMessage.setCreateTime(System.currentTimeMillis()); imageMessage.setImage(image); return imageMessageToXml(imageMessage);}

最后修改WeChatMqController中的text方法,增加一条判断,判断当用户输入数字2时,则回复图片消息。代码如下:

... if ("1".equals(allMessage.getContent())) { return MessageUtil.initNewsMessage(allMessage.getToUserName(), allMessage.getFromUserName()); } else if ("2".equals(allMessage.getContent())) { return MessageUtil.initImageMessage(allMessage.getToUserName(), allMessage.getFromUserName()); }...

完成以上代码的编写后,重启SpringBoot,打开微信公众号,测试结果如下:


音乐消息回复

在上一小节中,我们介绍了如何开发回复图片消息的功能,而其他类似的消息回复都是差不多的,这里就不一一去赘述了。本小节我们来看看如何进行音乐消息回复的开发,官方文档地址如下:

https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140543

回复音乐消息所需传递的参数如下:

官方的图文消息示例数据结构如下:

<xml> <ToUserName> <![CDATA[toUser]]> </ToUserName> <FromUserName> <![CDATA[fromUser]]> </FromUserName> <CreateTime>12345678</CreateTime> <MsgType> <![CDATA[music]]> </MsgType> <Music> <Title> <![CDATA[TITLE]]> </Title> <Description> <![CDATA[DESCRIPTION]]> </Description> <MusicUrl> <![CDATA[MUSIC_Url]]> </MusicUrl> <HQMusicUrl> <![CDATA[HQ_MUSIC_Url]]> </HQMusicUrl> <ThumbMediaId> <![CDATA[media_id]]> </ThumbMediaId> </Music></xml>

开发音乐消息回复,我们需要一个音乐文件,找一个mp3文件放在工程的resources/static目录下即可,并确保能够在外网上访问:

根据官方给出的数据结构,封装好各个实体类。Music 实体类代码如下:

package org.zero01.weixin.mqdemo.vo;import com.thoughtworks.xstream.annotations.XStreamAlias;import lombok.Data;@Datapublic 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;}

MusicMessage 实体类代码如下:

package org.zero01.weixin.mqdemo.vo;import com.thoughtworks.xstream.annotations.XStreamAlias;import lombok.Data;@Datapublic class MusicMessage extends BaseMassage { @XStreamAlias("Music") private Music music;}

由于音乐消息需要传递一个ThumbMediaId,也就是缩略图的媒体id。所以我们需要修改之前的测试代码,以此获取thumb_media_id,如下:

@Testpublic void upload() throws IOException { String filePath = "Z:/v2-9b17df91629f842edd472d7cfcaa9c4b_hd.jpg"; AccessToken accessToken = WeiXinUtil.getAccessToken(); String thumbMediaId = WeiXinUtil.upload(filePath, accessToken.getToken(), "thumb","thumb_media_id"); System.out.println("thumb_media_id: " + thumbMediaId);}

执行以上测试方法,控制台输出的结果如下:

thumb_media_id: Iu9puUGeFcS8HWyBGepJfeGoLDV_sWg8vJTeG-akMhcSGrqFjvoimMhCfjWw8F53

复制好thumb_media_id,然后在MessageUtil中新增如下两个方法:

/** * 将音乐消息转换为xml * * @param musicMessage * @return */public static String musicMessageToXml(MusicMessage musicMessage) { XStream xStream = new XStream(); xStream.processAnnotations(new Class[]{MusicMessage.class, Music.class}); xStream.alias("xml", musicMessage.getClass()); return xStream.toXML(musicMessage);}/** * 组装音乐消息对象 * * @param toUserName * @param fromUserName * @return */public static String initMusicMessage(String toUserName, String fromUserName) { Music music = new Music(); music.setTitle("音乐消息"); music.setDescription("这是一个音乐消息"); music.setThumbMediaId("Iu9puUGeFcS8HWyBGepJfeGoLDV_sWg8vJTeG-akMhcSGrqFjvoimMhCfjWw8F53"); music.setMusicUrl("http://zero.mynatapp.cc/Unravel.mp3"); music.setHQMusicUrl("http://zero.mynatapp.cc/Unravel.mp3"); MusicMessage musicMessage = new MusicMessage(); musicMessage.setFromUserName(toUserName); musicMessage.setToUserName(fromUserName); musicMessage.setMsgType(MessageTypeEnum.MSG_MUSIC.getMsgType()); musicMessage.setCreateTime(System.currentTimeMillis()); musicMessage.setMusic(music); return musicMessageToXml(musicMessage);}

最后修改WeChatMqController中的text方法,增加一条判断,判断当用户输入数字3时,则回复音乐消息。代码如下:

... if ("1".equals(allMessage.getContent())) { return MessageUtil.initNewsMessage(allMessage.getToUserName(), allMessage.getFromUserName()); } else if ("2".equals(allMessage.getContent())) { return MessageUtil.initImageMessage(allMessage.getToUserName(), allMessage.getFromUserName()); } else if ("3".equals(allMessage.getContent())) { return MessageUtil.initMusicMessage(allMessage.getToUserName(), allMessage.getFromUserName()); }...

完成以上代码的编写后,重启SpringBoot,打开微信测试公众号进行测试,测试结果如下:

点击音乐消息,打开后效果如下:

注:我这用的是pc端的微信,是可以正常播放的,但实际手机端很有可能无法播放,这也是微信的一个小坑