Java微信公众平台开发之消息管理

来源:互联网 发布:网络机顶盒不用连线 编辑:程序博客网 时间:2024/05/17 07:16

官方文档点击查看

微信消息管理分为接收普通消息、接收事件推送、发送消息(被动回复)、客服消息、群发消息、模板消息这几部分

一直没发消息处理的文章,一则本人菜鸟,二则之前封装的实在太烂了, 如有问题请指出,谢谢了

一、接收普通消息

当普通微信用户向公众账号发消息时,微信服务器将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/imageprivate 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;}@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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日 * */@Servicepublic class ReqMsgServiceImpl implements ReqMsService {private static final Logger logger = Logger.getLogger(ReqMsgServiceImpl.class);@Autowiredprivate 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格式类型的字符串 */@Overridepublic 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格式类型的字符串 */@Overridepublic WechatResult linkMsg(BasicMsg msg, Map<String, String> params) {return null;}/** * 默认执行的消息 *  * @param msg * @param params * @return 返回需要该消息回复的xml格式类型的字符串 */@Overridepublic WechatResult defaultMsg(BasicMsg msg, Map<String, String> params) {return null;}/** * 音乐执行的消息 *  * @param msg *            基础参数 * @param params *            请求参数 * @return 返回需要该消息回复的xml格式类型的字符串 */@Overridepublic WechatResult musicMsg(BasicMsg msg, Map<String, String> params) {return null;}/** * 图片消息 *  * @param msg * @param params * @return 返回需要该消息回复的xml格式类型的字符串 */@Overridepublic WechatResult imageMsg(BasicMsg msg, Map<String, String> params) {return null;}/** * 地理位置消息 *  * @param msg * @param params * @return 返回需要该消息回复的xml格式类型的字符串 */@Overridepublic WechatResult locationMsg(BasicMsg msg, Map<String, String> params) {return null;}/** * 语音消息 *  * @param msg * @param params * @return 返回需要该消息回复的xml格式类型的字符串 */@Overridepublic WechatResult voiceMsg(BasicMsg msg, Map<String, String> params) {return null;}/** * 视频消息 *  * @param msg *            消息基类 * @param params * @return 返回需要该消息回复的xml格式类型的字符串 */@Overridepublic WechatResult videoMsg(BasicMsg msg, Map<String, String> params) {return null;}/** * 小视频消息 *  * @param msg * @param params * @return 返回需要该消息回复的xml格式类型的字符串 */@Overridepublic 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 */@Overridepublic 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 */@Overridepublic WechatResult scan(BasicMsg msg, Map<String, String> params) {logger.info("已关注事件二维码参数" + params.get("EventKey"));/**** 逻辑处理 ****/return null;}/** * 上报地理位置事件 *  * @param msg * @param params * @return */@Overridepublic WechatResult eventLocation(BasicMsg msg, Map<String, String> params) {return null;}/** * 点击菜单拉取消息时的事件推送 (自定义菜单的click) *  * @param msg * @param params * @return */@Overridepublic WechatResult eventClick(BasicMsg msg, Map<String, String> params) {return null;}/** * 点击菜单跳转链接时的事件推送 (自定义菜单的view) *  * @param msg * @param params * @return */@Overridepublic WechatResult eventView(BasicMsg msg, Map<String, String> params) {return null;}/** * 事件类型默认返回 *  * @param msg * @param params * @return */@Overridepublic 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);@Autowiredprivate 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);}}}}

后续模板消息、客服消息再更新,暂时难剥离,后续再更新