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

阅读更多

官方文档点击查看

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

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

一、接收普通消息

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

关于MsgId,官方给出解释,相当于每个消息ID,关于重试的消息排重,推荐使用msgid排重。微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次。

比如文本消息的Xml示例

 


 
 
 1348831860
 
 
 1234567890123456
 

其他的消息去官方文档查看,简单封装如下

消息抽象基类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示例



12345678


简单封装下
回复消息抽象基类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 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 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 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 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 params) {

		return null;
	}

	/**
	 * 默认执行的消息
	 * 
	 * @param msg
	 * @param params
	 * @return 返回需要该消息回复的xml格式类型的字符串
	 */
	@Override
	public WechatResult defaultMsg(BasicMsg msg, Map params) {

		return null;
	}

	/**
	 * 音乐执行的消息
	 * 
	 * @param msg
	 *            基础参数
	 * @param params
	 *            请求参数
	 * @return 返回需要该消息回复的xml格式类型的字符串
	 */
	@Override
	public WechatResult musicMsg(BasicMsg msg, Map params) {

		return null;
	}

	/**
	 * 图片消息
	 * 
	 * @param msg
	 * @param params
	 * @return 返回需要该消息回复的xml格式类型的字符串
	 */
	@Override
	public WechatResult imageMsg(BasicMsg msg, Map params) {

		return null;
	}

	/**
	 * 地理位置消息
	 * 
	 * @param msg
	 * @param params
	 * @return 返回需要该消息回复的xml格式类型的字符串
	 */
	@Override
	public WechatResult locationMsg(BasicMsg msg, Map params) {

		return null;
	}

	/**
	 * 语音消息
	 * 
	 * @param msg
	 * @param params
	 * @return 返回需要该消息回复的xml格式类型的字符串
	 */
	@Override
	public WechatResult voiceMsg(BasicMsg msg, Map params) {

		return null;
	}

	/**
	 * 视频消息
	 * 
	 * @param msg
	 *            消息基类
	 * @param params
	 * @return 返回需要该消息回复的xml格式类型的字符串
	 */
	@Override
	public WechatResult videoMsg(BasicMsg msg, Map params) {

		return null;
	}

	/**
	 * 小视频消息
	 * 
	 * @param msg
	 * @param params
	 * @return 返回需要该消息回复的xml格式类型的字符串
	 */
	@Override
	public WechatResult shortvideo(BasicMsg msg, Map params) {

		return null;
	}

	/**
	 * 用户关注时调用的方法
	 *  用户未关注时进行关注后的事件推送/关注事件
	 * @param msg
	 * @param params
	 * @return
	 */
	@Override
	@Transactional(readOnly = false)
	public WechatResult subscribe(BasicMsg msg, Map params) {
		WechatResult result = new WechatResult();
		//SubscribeEvent event = new SubscribeEvent();
		//BeanUtils.populate(event, params);//转换失败
		String content = "欢迎关注我的个人博客" + "CSDN博客" + "博客园";
		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 sql = new HashMap();
		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 params) {
		Map sql = new HashMap();
		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 params) {
		logger.info("已关注事件二维码参数" + params.get("EventKey"));
		/**** 逻辑处理 ****/
		return null;
	}

	/**
	 * 上报地理位置事件
	 * 
	 * @param msg
	 * @param params
	 * @return
	 */
	@Override
	public WechatResult eventLocation(BasicMsg msg, Map params) {

		return null;
	}

	/**
	 * 点击菜单拉取消息时的事件推送 (自定义菜单的click)
	 * 
	 * @param msg
	 * @param params
	 * @return
	 */
	@Override
	public WechatResult eventClick(BasicMsg msg, Map params) {

		return null;
	}

	/**
	 * 点击菜单跳转链接时的事件推送 (自定义菜单的view)
	 * 
	 * @param msg
	 * @param params
	 * @return
	 */
	@Override
	public WechatResult eventView(BasicMsg msg, Map params) {

		return null;
	}

	/**
	 * 事件类型默认返回
	 * 
	 * @param msg
	 * @param params
	 * @return
	 */
	@Override
	public WechatResult eventDefaultReply(BasicMsg msg, Map 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:别问我要源码,我也是辛辛苦苦码出来的

你可能感兴趣的:(微信,微信消息,xml)