一、前言
hello小伙伴们,大家好,本篇的主题是使用Java开发微信公众号之被动回复用户消息-回复图文消息
,那么对于不太了解微信公众号被动回复用户消息(文本消息、图片消息)
的小伙伴们,可以先看一下前面我写过的文章: Java开发微信公众号之被动回复用户消息-回复文本消息 , 废话不多说,下面开始进入主题。
微信开发文档: 微信官方文档-被动回复用户消息
二、版本说明
- spring boot.version: v2.1.7.RELEASE
- java.version: 1.8
- weixin-java-mp.version: 3.5.0
三、被动回复消息
当用户发送消息给公众号时(或某些特定的用户操作引发的事件推送时),会产生一个POST
请求,开发者可以在响应包(Get
)中返回特定XML结构,来对该消息进行响应(现支持回复文本
、图片
、图文
、语音
、视频
、音乐
)。严格来说,发送被动响应消息其实并不是一种接口,而是对微信服务器发过来消息的一次回复。
四、消息排重
微信服务器在将用户的消息发给公众号的开发者服务器URL
地址(开发者中心处配置)后,微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次,如果在Debug
调试中,发现用户无法收到响应的消息,可以检查是否消息处理超时,一般debug
调试的情况下,超过5秒就会收不到响应。关于重试的消息排重,每一次消息都应该保持其幂等性,在推送的xml
数据结构中,有msgid
的消息推荐使用msgid
排重。如果是事件类型消息推荐使用FromUserName + CreateTime
进行排重。
五、如何保证五秒内处理并回复
假如服务器无法保证在五秒内处理并回复,必须做出下述回复,这样微信服务器才不会对此作任何处理,并且不会发起重试(这种情况下,可以使用客服消息接口进行异步回复),否则,将出现严重的错误提示。详见下面说明:
- 1、直接回复
success
(推荐方式) - 2、直接回复空串(指字节长度为0的空字符串,而不是
XML
结构体中content
字段的内容为空)
一旦遇到服务器五秒内没有做处理并且响应回复或者开发者回复了异常数据,比如JSON
数据等;微信都会在公众号会话中,向用户下发系统提示“该公众号暂时无法提供服务,请稍后再试”
;
六、如何回复图文消息
回复图片(不支持gif动图)多媒体消息时需要预先通过素材管理接口上传临时素材到微信服务器;也可以通过上传素材管理接口中的永久素材到微信微服务器,只不过临时素材在微信服务器上存储的有效期只有3天,而永久素材永远有效,由于微信永久素材的数量有上限,如果你的项目环境对对媒体素材文件需求量很大,谨慎使用永久素材上传,使用临时素材即可;不过也没关系,微信开发者文档也提供了删除永久素材和删除临时素材的接口,根据场景合理使用即可。
通过素材管理接口上传完临时素材或者永久素材后,接口会返回一个关键字段叫做mediaId
,这个mediaId
返回给我们之后需要我们自己存储记录起来,等到需要使用多媒体文件素材的时候,都是通过传递mediaId
到接口参数中,微信服务器会根据该mediaId
去查找对应的素材文件,然后做对应的回复处理,比如回复图片信息、回复图文信息等等。
七、预先上传图片素材
请注意: 前面已经强调过,回复图片(不支持gif动图)
等多媒体消息时需要预先通过素材管理接口上传临时素材到微信服务器,可以使用素材管理中的临时素材,也可以使用永久素材。
所以我们在实现被动回复消息-回复图文消息的功能之前,需要先上传图片临时素材或者图片永久素材到微信平台,接下来我们先观察下图文消息所需要的xml格式
和参数说明。
1. 回复图文消息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>
2. 回复图文消息参数说明
参数 | 是否必须 | 说明 |
---|---|---|
ToUserName | 是 | 接收方帐号(收到的OpenID) |
FromUserName | 是 | 开发者微信号 |
CreateTime | 是 | 消息创建时间 (整型) |
MsgType | 是 | 消息类型,图文为news |
ArticleCount | 是 | 图文消息个数;当用户发送文本、图片、语音、视频、图文、地理位置这六种消息时,开发者只能回复1条图文消息;其余场景最多可回复8条图文消息 |
Articles | 是 | 图文消息信息,注意,如果图文数超过限制,则将只发限制内的条数 |
Title | 是 | 图文消息标题 |
Description | 是 | 图文消息描述 |
PicUrl | 是 | 图片链接,支持JPG、PNG格式,较好的效果为大图360200,小图200200 |
Url | 是 | 点击图文消息跳转链接 |
3. 上传永久图片素材
/**
* @desc:
* @author: cao_wencao
* @date: 2020-05-21 13:47
*/
@Slf4j
@RestController
@RequestMapping("/wx/material")
public class WxMaterialController {
private static final SimpleDateFormat sdf =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS" );
@Autowired
private WeChatMaterialUtils materialUtils;
/**
* 上传单个图片文件
*/
@PostMapping("/uploadImg")
public ApiResult uploadImg(@RequestParam("imgFile") MultipartFile imgFile) throws IOException {
log.info("multipartFile = " + imgFile);
log.info("ContentType = " + imgFile.getContentType());
log.info("OriginalFilename = " + imgFile.getOriginalFilename());
log.info("Name = " + imgFile.getName());
log.info("Size = " + imgFile.getSize());
String fileExt = imgFile.getOriginalFilename().substring(imgFile.getOriginalFilename().lastIndexOf(".") + 1);
log.info("fileExt = " + fileExt);
WxMpMaterialUploadResult wxMpMaterialUploadResult
= materialUtils.uploadFilesToWeChat("image", fileExt ,imgFile.getName(), imgFile.getInputStream());
if(null == wxMpMaterialUploadResult){
return ApiResult.error("上传图片失败");
}
log.info("wxMpMaterialUploadResult = : " + JSON.toJSONString(wxMpMaterialUploadResult));
return ApiResult.succee(wxMpMaterialUploadResult,"上传图片成功");
}
}
4. PostMan测试图片上传
4.1 传参如下
- headers
“key”:“Content-Type”, “value”:“multipart/form-data”
- body
- 响应结果
{
"code": 200,
"message": "上传图片成功",
"data": {
"mediaId": "1C72rnlYrj7ZqBiRGdKCoUUudPCjA5qMkJZeODvTN9U",
"url": "http://mmbiz.qpic.cn/mmbiz_jpg/DwMlrmia5oVQrbNsb6GJ64xlUtfTXspTcyuV1m6ykgiaGJQYR374WWfGGrgibJOibYc1df7Wyicw4u9f5CV3u7oAzow/0?wx_fmt=jpeg"
}
}
- 访问url
八、封装图文消息Handler
- TextMsgHandler.java
package com.thinkingcao.weixin.handler;
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutNewsMessage;
import me.chanjar.weixin.mp.bean.result.WxMpUser;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* @desc: 文本类型消息处理-TEXT
* @link: XmlMsgType.TEXT
* @author: cao_wencao
* @date: 2020-05-20 15:15
*/
@Component
public class TextMsgHandler extends AbstractHandler {
@Override
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> map, WxMpService wxMpService, WxSessionManager wxSessionManager) throws WxErrorException {
//判断传递过来的消息,类型是否为TEXT
if (wxMessage.getMsgType().equals(WxConsts.XmlMsgType.TEXT)) {
//TODO: 如果需要做微信消息日志存储,可以在这里进行日志存储到数据库,这里省略不写。
}
// 获取微信用户基本信息
WxMpUser userWxInfo = wxMpService.getUserService().userInfo(wxMessage.getFromUser(), "zh_CN");
if (null == userWxInfo){
return null;
}
String content = wxMessage.getContent();
if ("文本".equals(content)){
//下面两种响应方式都可以
//return new TextBuilder().build("您的一互动,泛起了我内心的涟漪。",wxMessage,wxMpService);
return WxMpXmlOutMessage
.TEXT()
.content("您的一互动,就激起了我内心的无限可能")
.fromUser(wxMessage.getToUser())
.toUser(wxMessage.getFromUser())
.build();
}
if ("图片".equals(content)){
return WxMpXmlOutMessage
.IMAGE()
.mediaId("1C72rnlYrj7ZqBiRGdKCoS54AXQwSo4iULd9qRhOC-U")
.fromUser(wxMessage.getToUser())
.toUser(wxMessage.getFromUser())
.build();
}
if ("图文".equals(content)){
List<WxMpXmlOutNewsMessage.Item> articles = new ArrayList<>();
WxMpXmlOutNewsMessage.Item item = new WxMpXmlOutNewsMessage.Item();
item.setDescription("使用Java语言进行开发微信公众号发送图文消息");
item.setPicUrl("http://mmbiz.qpic.cn/mmbiz_jpg/DwMlrmia5oVQrbNsb6GJ64xlUtfTXspTcgAU6Jvbt3uL72LqN3ToB4ibiaWkTZT7SD0IUD56uGiaFRXpI8vBYtYrAQ/0?wx_fmt=jpeg");
item.setTitle("SpringBoot开发微信公众号");
item.setUrl("https://blog.csdn.net/thinkingcao/category_9277860.html");
articles.add(item);
return WxMpXmlOutMessage
.NEWS()
.addArticle(item)
.articles(articles)
.fromUser(wxMessage.getToUser())
.toUser(wxMessage.getFromUser())
.build();
}
return null;
}
}
九、测试被动回复图文消息
在公众号对话框内输入: 图文 关键字后,后台会通过路由匹配该消息类型MsgType = text ,然后找到具体的文本消息处理器,也就是TextMsgHandler,然后进一步匹配关键字‘图文’,然后组装回复图文消息字段,将图文消息回复给用户互动者,那么这里简单插一句,在实际项目开发中,构建被动消息回复肯定是要实现动态的内容,那么所有字段产生的数据肯定都是要通过数据库表记录的,在构建被动消息回复时,从数据库取即可,这样一来就实现了动态被动回复用户消息。
- 后台请求响应控制台日志
2020-06-10 23:51:23.334 DEBUG 4812 --- [nio-8080-exec-3] m.c.w.mp.api.impl.BaseWxMpServiceImpl :
【请求地址】: https://api.weixin.qq.com/cgi-bin/user/info?access_token=34_Gsg49pv1E8sqzyc7DeHUXSVtUQZSxxTXkXp3h_S_MCOXYwIuqqH41B4a6eF3HpokFucpJVjJIPpPvKwQYCXFMWdAJc45Yd8BmEk-PPHWDg_iJ5INbTuWn9m3gQdHQV-zcbtFN2V8-DGDZo6WSHXfAAAWLB
【请求参数】:openid=oGjQdw2EyT7CBNfN84Te6IpmflCM&lang=zh_CN
【响应数据】:{
"subscribe":1,"openid":"oGjQdw2EyT7CBNfN84Te6IpmflCM","nickname":"曹","sex":1,"language":"zh_CN","city":"墨尔本","province":"维多利亚","country":"澳大利亚","headimgurl":"http:\/\/thirdwx.qlogo.cn\/mmopen\/1ZMUBCDTp8ZAsxH99cX3icFXXDSstNaIR1FDpibnmfNPEn1J7Hf9yLXicSHJiciaEgtwgTXRicib9X2mua4bpeEg2sWNics6rXnIKKq7\/132","subscribe_time":1589956861,"remark":"","groupid":0,"tagid_list":[],"subscribe_scene":"ADD_SCENE_QR_CODE","qr_scene":0,"qr_scene_str":""}
2020-06-10 23:51:23.335 DEBUG 4812 --- [nio-8080-exec-3] m.c.weixin.mp.api.WxMpMessageRouter : End session access: async=false, sessionId=oGjQdw2EyT7CBNfN84Te6IpmflCM
2020-06-10 23:51:23.335 DEBUG 4812 --- [pool-1-thread-4] m.c.weixin.mp.api.WxMpMessageRouter : End session access: async=true, sessionId=oGjQdw2EyT7CBNfN84Te6IpmflCM
2020-06-10 23:51:23.336 DEBUG 4812 --- [nio-8080-exec-3] c.t.w.controller.WxPortalController :
组装回复信息:<xml>
<ToUserName><![CDATA[oGjQdw2EyT7CBNfN84Te6IpmflCM]]></ToUserName>
<FromUserName><![CDATA[gh_833ac613acf7]]></FromUserName>
<CreateTime>1591804283</CreateTime>
<MsgType><![CDATA[news]]></MsgType>
<Articles>
<item>
<Title><![CDATA[SpringBoot开发微信公众号]]></Title>
<Description><![CDATA[使用Java语言进行开发微信公众号发送图文消息]]></Description>
<PicUrl><![CDATA[http://mmbiz.qpic.cn/mmbiz_jpg/DwMlrmia5oVQrbNsb6GJ64xlUtfTXspTcyuV1m6ykgiaGJQYR374WWfGGrgibJOibYc1df7Wyicw4u9f5CV3u7oAzow/0?wx_fmt=jpeg]]></PicUrl>
<Url><![CDATA[https://blog.csdn.net/thinkingcao/category_9277860.html]]></Url>
</item>
</Articles>
<ArticleCount>1</ArticleCount>
</xml>
- 对话框互动
十、源码
源码: https://github.com/Thinkingcao/SpringBootLearning/tree/master/springboot-wechat