Java微信公众平台开发之消息管理
程序员文章站
2022-05-29 23:21:46
...
官方文档点击查看
微信消息管理分为接收普通消息、接收事件推送、发送消息(被动回复)、客服消息、群发消息、模板消息这几部分
一直没发消息处理的文章,一则本人菜鸟,二则之前封装的实在太烂了, 如有问题请指出,谢谢了
一、接收普通消息
当普通微信用户向公众账号发消息时,微信服务器将POST消息的XML数据包到开发者填写的URL上。
关于MsgId,官方给出解释,相当于每个消息ID,关于重试的消息排重,推荐使用msgid排重。微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次。
比如文本消息的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>
其他的消息去官方文档查看,简单封装如下
消息抽象基类AbstractMsg.java
package com.phil.wechatmsg.model.req; /** * 基础消息类 * * @author phil * */ public abstract class AbstractMsg { private String ToUserName; // 开发者微信号 private String FromUserName; // 发送方帐号(一个OpenID) private String MsgType = SetMsgType(); // 消息类型 例如 /text/image private long CreateTime; // 消息创建时间 (整型) private long MsgId; // 消息id,64位整型 /** * 消息类型 * * @return */ public abstract String SetMsgType(); 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 long getMsgId() { return MsgId; } public void setMsgId(long msgId) { MsgId = msgId; } public String getMsgType() { return MsgType; } }
文本消息TextMsg.java
package com.phil.wechatmsg.model.req; /** * 文本消息 * @author phil * @date 2017年6月30日 * */ public class TextMsg extends AbstractMsg { private String Content; // 文本消息 public String getContent() { return Content; } public void setContent(String content) { Content = content; } @Override public String SetMsgType() { return "text"; } }
其他的依样画葫芦......
二、被动回复用户消息
微信服务器在将用户的消息发给公众号的开发者服务器地址(开发者中心处配置)后,微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次,如果在调试中,发现用户无法收到响应的消息,可以检查是否消息处理超时。假如服务器无法保证在五秒内处理并回复,可以直接回复空串,微信服务器不会对此作任何处理,并且不会发起重试。
如果出现“该公众号暂时无法提供服务,请稍后再试”,原因有两个
- 开发者在5秒内未回复任何内容
- 开发者回复了异常数据
比如回复的文本消息Xml示例
<xml> <ToUserName><![CDATA[toUser]]></ToUserName> <FromUserName><![CDATA[fromUser]]></FromUserName> <CreateTime>12345678</CreateTime> <MsgType><![CDATA[text]]></MsgType> <Content><![CDATA[你好]]></Content> </xml>简单封装下
回复消息抽象基类RespAbstractMsg.java
package com.phil.wechatmsg.model.resp; /** * 消息基类(公众帐号 -> 普通用户) * @author phil * */ public abstract class RespAbstractMsg { // 接收方帐号(收到的OpenID) private String ToUserName; // 开发者微信号 private String FromUserName; // 消息创建时间 (整型) private long CreateTime; // 消息类型(text/music/news) private String MsgType = setMsgType(); // 消息类型 public abstract String setMsgType(); 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; } }回复文本消息RespTextMsg.java
package com.phil.wechatmsg.model.resp; /** * 文本消息(公众帐号 -> 普通用户) * @author phil * */ public class RespTextMsg extends RespAbstractMsg { //回复的消息内容 private String Content; public String getContent() { return Content; } public void setContent(String content) { Content = content; } @Override public String setMsgType() { return "text"; } }回复图片消息RespImageMsg.java
package com.phil.wechatmsg.model.resp; import com.phil.wechatmsg.model.resp.bean.Image; /** * 回复图片消息 * @author phil * @data 2017年3月26日 * */ public class RespImageMsg extends RespAbstractMsg { private Image Image; public Image getImage() { return Image; } public void setImage(Image image) { Image = image; } @Override public String setMsgType() { return "image"; } }回复图片Image.java
package com.phil.wechatmsg.model.resp.bean; /** * * @author phil * @date 2017年7月19日 * */ public class Image { // 通过素材管理中的接口上传多媒体文件,得到的id。 private String MediaId; public String getMediaId() { return MediaId; } public void setMediaId(String mediaId) { MediaId = mediaId; } }其他消息类型依样画葫芦......
接收事件推送、客服消息、群发消息、模板消息等等也是依此封装,后续再更新代码
三、消息的处理
之前我是之前在controller直接写方法以if依次判断,这样写的太low,以下实现基于jdk1.7
请求消息(接收普通消息)处理ReqMsServiceImpl.java,贴出了部分代码,其他的可参考实现
package com.phil.wechatmsg.service.impl; import java.io.InputStream; import java.io.Serializable; import java.util.HashMap; import java.util.Map; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.dom4j.DocumentException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.phil.common.dao.GenericDao; import com.phil.common.result.WechatResult; import com.phil.common.util.DateTimeUtil; import com.phil.common.util.MsgUtil; import com.phil.common.util.XStreamFactroy; import com.phil.common.util.XmlUtil; import com.phil.wechatmsg.model.req.BasicMsg; import com.phil.wechatmsg.model.resp.RespAbstractMsg; import com.phil.wechatmsg.model.resp.RespNewsMsg; import com.phil.wechatmsg.model.resp.RespTextMsg; import com.phil.wechatmsg.service.ReqMsService; import com.phil.wechatuser.entity.WechatUser; import com.thoughtworks.xstream.XStream; /** * 请求消息处理 * * @author phil * @date 2017年7月21日 * */ @Service public class ReqMsgServiceImpl implements ReqMsService { private static final Logger logger = Logger.getLogger(ReqMsgServiceImpl.class); @Autowired private GenericDao<WechatUser, Serializable> wechatUserDao; /** * 请求消息类型:文本 */ public static final String REQ_MESSAGE_TYPE_TEXT = "text"; /** * 请求消息类型:图片 */ public static final String REQ_MESSAGE_TYPE_IMAGE = "image"; /** * 请求消息类型:链接 */ public static final String REQ_MESSAGE_TYPE_LINK = "link"; /** * 请求消息类型:地理位置 */ public static final String REQ_MESSAGE_TYPE_LOCATION = "location"; /** * 请求消息类型:音频 */ public static final String REQ_MESSAGE_TYPE_VOICE = "voice"; /** * 请求消息类型:视频 */ public static final String REQ_MESSAGE_TYPE_VIDEO = "video"; /** * 请求消息类型: 短视频消息 */ public static final String REQ_MESSAGE_TYPE_SHORTVIDEO = "shortvideo"; /** * 请求消息类型:推送 */ public static final String REQ_MESSAGE_TYPE_EVENT = "event"; /** * 事件类型:subscribe(订阅) */ public static final String EVENT_TYPE_SUBSCRIBE = "subscribe"; /** * 事件类型:unsubscribe(取消订阅) */ public static final String EVENT_TYPE_UNSUBSCRIBE = "unsubscribe"; /** * 事件类型:CLICK(自定义菜单点击事件) */ public static final String EVENT_TYPE_CLICK = "CLICK"; /** * 事件类型: view(自定义菜单view事件) */ public static final String EVENT_TYPE_VIEW = "VIEW"; /** * 事件类型:scan(用户已关注时的事件推送) */ public static final String EVENT_TYPE_SCAN = "SCAN"; /** * 事件类型:LOCATION(上报地理位置事件) */ public static final String EVENT_TYPE_LOCATION = "LOCATION"; /** * 默认处理方法 * @param input * @return * @throws Exception * @throws DocumentException */ public String defaultMsgDisPose(InputStream inputStream) throws Exception { String result = null; if (inputStream != null) { Map<String, String> params = XmlUtil.parseXmlToMap(inputStream); if (params != null && params.size() > 0) { BasicMsg msgInfo = new BasicMsg(); String createTime = params.get("CreateTime"); String msgId = params.get("MsgId"); msgInfo.setCreateTime((createTime != null && !"".equals(createTime)) ? Integer.parseInt(createTime) : 0); msgInfo.setFromUserName(params.get("FromUserName")); msgInfo.setMsgId((msgId != null && !"".equals(msgId)) ? Long.parseLong(msgId) : 0); msgInfo.setToUserName(params.get("ToUserName")); WechatResult resultObj = msgDispose(msgInfo, params); if(resultObj==null){ // return null; } boolean success = resultObj.isSuccess(); //如果 为true,则表示返回xml文件, 直接转换即可,否则按类型 if (success) { result = resultObj.getObject().toString(); } else { int type = resultObj.getType(); // 这里规定 1 图文消息 否则直接转换 if (type == WechatResult.NEWSMSG) { RespNewsMsg newsMsg = (RespNewsMsg) resultObj.getObject(); result = MsgUtil.NewsMsg(newsMsg); } else { RespAbstractMsg basicMsg = (RespAbstractMsg) resultObj.getObject(); result = toMsgXml(basicMsg); } } } else { result = "msg is wrong"; } } return result; } /** * 核心处理方法 * * @param msg * 消息基类 * @param params * xml 解析出来的 数据 * @return */ private WechatResult msgDispose(BasicMsg msg, Map<String, String> params) { WechatResult result = null; String msgType = params.get("MsgType"); if (StringUtils.isNotBlank(msgType)) { switch (msgType) { case REQ_MESSAGE_TYPE_TEXT: // 文本消息 result = textMsg(msg, params); break; case REQ_MESSAGE_TYPE_IMAGE: // 图片消息 result = imageMsg(msg, params); break; case REQ_MESSAGE_TYPE_LINK: // 链接消息 result = linkMsg(msg, params); break; case REQ_MESSAGE_TYPE_LOCATION: // 地理位置 result = locationMsg(msg, params); break; case REQ_MESSAGE_TYPE_VOICE: // 音频消息 result = voiceMsg(msg, params); break; case REQ_MESSAGE_TYPE_SHORTVIDEO: // 短视频消息 result = shortvideo(msg, params); break; case REQ_MESSAGE_TYPE_VIDEO: // 视频消息 result = videoMsg(msg, params); break; case REQ_MESSAGE_TYPE_EVENT: // 事件消息 String eventType = params.get("Event"); // if (eventType != null && !"".equals(eventType)) { switch (eventType) { case EVENT_TYPE_SUBSCRIBE: result = subscribe(msg, params); break; case EVENT_TYPE_UNSUBSCRIBE: result = unsubscribe(msg, params); break; case EVENT_TYPE_SCAN: result = scan(msg, params); break; case EVENT_TYPE_LOCATION: result = eventLocation(msg, params); break; case EVENT_TYPE_CLICK: result = eventClick(msg, params); break; case EVENT_TYPE_VIEW: result = eventView(msg, params); break; case KF_CREATE_SESSION: result = kfCreateSession(msg, params); break; case KF_CLOSE_SESSION: result = kfCloseSession(msg, params); break; case KF_SWITCH_SESSION: result = kfSwitchSession(msg, params); break; default: eventDefaultReply(msg, params); break; } } break; default: defaultMsg(msg, params); } } return result; } /** * 将java对象转换为xml * * @return 已经转换好的xml格式字符 */ public String toMsgXml(RespAbstractMsg msg) { String result = ""; if (msg != null) { XStream xs = XStreamFactroy.init(true); xs.alias("xml", msg.getClass()); result = xs.toXML(msg); } return result; } /** * 处理用户发送的为文本消息 * * @param msg * 基础消息 * @param params * 请求参数 * @return 返回需要该消息回复的xml格式类型的字符串 */ @Override public WechatResult textMsg(BasicMsg msg, Map<String, String> params) { WechatResult result = new WechatResult(); RespTextMsg text = new RespTextMsg(); text.setContent(params.get("Content").trim());//自动回复 text.setCreateTime(DateTimeUtil.currentTime()); text.setToUserName(msg.getFromUserName()); text.setFromUserName(msg.getToUserName()); result.setObject(text); return result; } /** * 链接消息 * * @param msg * @param params * @return 返回需要该消息回复的xml格式类型的字符串 */ @Override public WechatResult linkMsg(BasicMsg msg, Map<String, String> params) { return null; } /** * 默认执行的消息 * * @param msg * @param params * @return 返回需要该消息回复的xml格式类型的字符串 */ @Override public WechatResult defaultMsg(BasicMsg msg, Map<String, String> params) { return null; } /** * 音乐执行的消息 * * @param msg * 基础参数 * @param params * 请求参数 * @return 返回需要该消息回复的xml格式类型的字符串 */ @Override public WechatResult musicMsg(BasicMsg msg, Map<String, String> params) { return null; } /** * 图片消息 * * @param msg * @param params * @return 返回需要该消息回复的xml格式类型的字符串 */ @Override public WechatResult imageMsg(BasicMsg msg, Map<String, String> params) { return null; } /** * 地理位置消息 * * @param msg * @param params * @return 返回需要该消息回复的xml格式类型的字符串 */ @Override public WechatResult locationMsg(BasicMsg msg, Map<String, String> params) { return null; } /** * 语音消息 * * @param msg * @param params * @return 返回需要该消息回复的xml格式类型的字符串 */ @Override public WechatResult voiceMsg(BasicMsg msg, Map<String, String> params) { return null; } /** * 视频消息 * * @param msg * 消息基类 * @param params * @return 返回需要该消息回复的xml格式类型的字符串 */ @Override public WechatResult videoMsg(BasicMsg msg, Map<String, String> params) { return null; } /** * 小视频消息 * * @param msg * @param params * @return 返回需要该消息回复的xml格式类型的字符串 */ @Override public WechatResult shortvideo(BasicMsg msg, Map<String, String> params) { return null; } /** * 用户关注时调用的方法 * 用户未关注时进行关注后的事件推送/关注事件 * @param msg * @param params * @return */ @Override @Transactional(readOnly = false) public WechatResult subscribe(BasicMsg msg, Map<String, String> params) { WechatResult result = new WechatResult(); //SubscribeEvent event = new SubscribeEvent(); //BeanUtils.populate(event, params);//转换失败 String content = "欢迎关注我的个人博客" + "<a href=\"http://blog.csdn.net/phil_jing\">CSDN博客</a>" + "<a href=\"http://www.cnblogs.com/phil_jing\">博客园</a>"; RespTextMsg text = new RespTextMsg(); text.setContent(content); text.setCreateTime(DateTimeUtil.currentTime()); text.setToUserName(msg.getFromUserName()); text.setFromUserName(msg.getToUserName()); result.setObject(text); logger.info(DateTimeUtil.formatDate(text.getCreateTime(), DateTimeUtil.YMDHMS_DATEFORMA) + "关注的openid:" + msg.getFromUserName()); // 保存 /****EventKey,Ticket处理 不为空说明有参数****/ if(params.get("EventKey")!=null){ logger.info("二维码"+params.get("EventKey")); /**** 逻辑处理 ****/ } Map<String,Object> sql = new HashMap<String,Object>(); sql.put("openid", msg.getFromUserName()); WechatUser user = wechatUserDao.findFirstByHQL(WechatUser.class, "from WechatUser where openid = :openid", sql); if(user==null){ user = new WechatUser(); user.setSubscribe(1); user.setCreatTime(text.getCreateTime()); wechatUserDao.save(user); }else{ user.setSubscribe(1); //以前关注过的 user.setUpdateTime(text.getCreateTime()); wechatUserDao.update(user); } return result; } /** * 取消关注时调用的方法 * * @param msg * @param params * @return */ @Override public WechatResult unsubscribe(BasicMsg msg, Map<String, String> params) { Map<String,Object> sql = new HashMap<String,Object>(); sql.put("openid", msg.getFromUserName()); WechatUser user = wechatUserDao.findFirstByHQL(WechatUser.class, "from WechatUser where openid = :openid", sql); if(user==null){ user = new WechatUser(); user.setSubscribe(0); user.setCreatTime(DateTimeUtil.currentTime()); wechatUserDao.save(user); }else{ user.setSubscribe(0); //以前关注过的 user.setUpdateTime(DateTimeUtil.currentTime()); wechatUserDao.update(user); } return null; } /** * 用户已关注时的事件推送 * * @param msg * @param params * @return */ @Override public WechatResult scan(BasicMsg msg, Map<String, String> params) { logger.info("已关注事件二维码参数" + params.get("EventKey")); /**** 逻辑处理 ****/ return null; } /** * 上报地理位置事件 * * @param msg * @param params * @return */ @Override public WechatResult eventLocation(BasicMsg msg, Map<String, String> params) { return null; } /** * 点击菜单拉取消息时的事件推送 (自定义菜单的click) * * @param msg * @param params * @return */ @Override public WechatResult eventClick(BasicMsg msg, Map<String, String> params) { return null; } /** * 点击菜单跳转链接时的事件推送 (自定义菜单的view) * * @param msg * @param params * @return */ @Override public WechatResult eventView(BasicMsg msg, Map<String, String> params) { return null; } /** * 事件类型默认返回 * * @param msg * @param params * @return */ @Override public WechatResult eventDefaultReply(BasicMsg msg, Map<String, String> params) { return null; } }
四、开发者服务器地址
WechatController.java
package com.phil.wechatmsg.controller; import java.io.IOException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import com.phil.common.util.SignatureUtil; import com.phil.controller.WeChatController; import com.phil.wechatmsg.service.ReqMsService; @Controller @RequestMapping("/wechat") public class WechatController { private static final Logger logger = Logger.getLogger(WeChatController.class); @Autowired private ReqMsService reqMsService; /** * 校验信息是否是从微信服务器发出,处理消息 * @param request * @param out * @throws IOException */ @RequestMapping(value = "/dispose", method = { RequestMethod.GET, RequestMethod.POST }) public void processPost(HttpServletRequest request, HttpServletResponse response) throws Exception { request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); boolean ispost = request.getMethod().toUpperCase().equals("POST"); if (ispost) { logger.info("接入成功,正在处理逻辑"); String respXml = reqMsService.defaultMsgDisPose(request.getInputStream());//processRequest(request, response); if (StringUtils.isNotBlank(respXml)) { // 响应消息 response.getWriter().write(respXml); } } else { String signature = request.getParameter("signature"); // 时间戳 String timestamp = request.getParameter("timestamp"); // 随机数 String nonce = request.getParameter("nonce"); // 通过检验signature对请求进行校验,若校验成功则原样返回echostr,表示接入成功,否则接入失败 if (SignatureUtil.checkSignature(signature, timestamp, nonce)) { // 随机字符串 String echostr = request.getParameter("echostr"); logger.info("接入成功,echostr=" + echostr); response.getWriter().write(echostr); } } } }
后续模板消息、客服消息再更新,暂时难剥离,后续再更新
PS:别问我要源码,我也是辛辛苦苦码出来的