在上篇《微信公众号的二次开发(二 自定义菜单的创建)》中我们介绍了自定义菜单的创建。本篇文章将介绍如何通过接收事件推送来获取用户信息。首先我们阅读官方文档:
根据官方文档的介绍 微信公众号会将时间推送到开发者填写的URL上,这个URL就是我们在
微信公众号的二次开发(一 订阅号没有获取网页授权的解决方法) 中配置的服务器配置的地址。这里还有一点要注意,在配置服务器地址时,官方文档上明确注明接口为GET方法。
而在接受推送事件时,我们就要用POST方法来接受微信后台传递的数据。
@ApiOperation("Used to Verify the Signature of Wechat Public Platform")
@RequestMapping(value = "checkSignature", method = RequestMethod.POST)
public void responseEvent(HttpServletRequest req, HttpServletResponse resp,HttpSession session) throws Exception {
req.setCharacterEncoding("UTF-8");
resp.setCharacterEncoding("UTF-8");
PrintWriter out = resp.getWriter();
String message = "success";
try {
//把微信返回的xml信息转义成map
Map<String, String> map = XmlUtil.xmlToMap(req);
String fromUserName = map.get("FromUserName");//消息来源用户标识 发送方帐号(一个OpenID)
String toUserName = map.get("ToUserName");//消息目的用户标识
String msgType = map.get("MsgType");//消息类型
String content = map.get("Content");//消息内容
String eventType = map.get("Event");
String key=map.get("EventKey");
MessageUtil messageUtil=new MessageUtil();
if(MessageUtil.MSGTYPE_EVENT.equals(msgType)){//如果为事件类型
if(MessageUtil.MESSAGE_SUBSCIBE.equals(eventType)){//处理订阅事件
message = messageUtil.subscribeForText(toUserName, fromUserName,session);
}else if(MessageUtil.MESSAGE_UNSUBSCIBE.equals(eventType)){//处理取消订阅事件
message = messageUtil.unsubscribe(fromUserName);
}else if(MessageUtil.MESSAGE_CLICK.equals(eventType)){
//根据自定义菜单中的key来进行业务处理
if(key.equals("32")){
content="<a href='http://www.mdline.cn'>健康档案</a>";
message = messageUtil.textMsg(toUserName, fromUserName,content);
}
}
}else{
//微信消息分为事件推送消息和普通消息,非事件即为普通消息类型
switch (msgType) {
case "text":{//文本消息
if(content.equals("测试")){
content ="这是开发测试";
}else{
content ="未能识别,详情访问:http://www.mdline.cn";
}
message = messageUtil.textMsg(toUserName, fromUserName,content);
break;
}
default:{//其他类型的普通消息
break;
}
}
}
} catch (DocumentException e) {
e.printStackTrace();
}finally{
out.println(message);
if(out!=null){
out.close();
}
}
}
下面是处理订阅事件即关注公众号时的方法:
/*
* 响应订阅事件--回复文本消息
*/
public String subscribeForText(String toUserName,String fromUserName,HttpSession session){
//订阅成功后,根据openId获取用户信息 并注册。 fromUserName即为该用户的openid。
//因为订阅号没有获取用户信息授权的权限,code获取不到。无法用code换取openid。因此获取openid后直接调用获取用户信息的方法
// 获取基础刷新的接口访问凭证(目前还没明白为什么用authInfo.get("AccessToken");这里面的access_token就不行)
String accessToken = WeiXinUtils.getAccessToken().getToken();
try {
messageUtil.userService.getUserInfo(accessToken,fromUserName,session);
} catch (IOException e) {
e.printStackTrace();
}
return textMsg(toUserName, fromUserName, "欢迎关注,精彩内容不容错过!!!,详情请访问网址:http://www.mdline.cn");
}
在这里 因为springboot无法扫描到工具类中的userService 所以我们要在工具类中添加下面的代码来将其注册到bean中。
@Autowired
private UserService userService;
public static MessageUtil messageUtil ;
@PostConstruct
public void init() {
messageUtil = this;
messageUtil.userService=this.userService;
//初使化时将已静态化的accessTokenOcrRepository实例化
}
这样我们就可以在工具类中调用userService的方法。
@Override
public User getUserInfo(String accessToken, String openId,HttpSession session) throws IOException {
User weixinUserInfo = null;
// 拼接获取用户信息接口的请求地址
String requestUrl = ProjectConst.GET_WEIXIN_USER_URL.replace("ACCESS_TOKEN", accessToken).replace(
"OPENID", openId);
// 获取用户信息(返回的是Json格式内容)
JSONObject jsonObject = WeiXinUtils.doGetStr(requestUrl);
if (null != jsonObject) {
try {
//封装获取到的用户信息
weixinUserInfo = new User();
// 用户的标识
weixinUserInfo.setOpenId(jsonObject.getString("openid"));
// 昵称
weixinUserInfo.setNickName(jsonObject.getString("nickname"));
// 用户的性别(1是男性,2是女性,0是未知)
weixinUserInfo.setSex(jsonObject.getInteger("sex"));
weixinUserInfo.setLanguage(jsonObject.getString("zh_CN"));
weixinUserInfo.setSubscribeTime(jsonObject.getString("subscribe_time"));
// 用户所在国家
weixinUserInfo.setCountry(jsonObject.getString("country"));
// 用户所在省份
weixinUserInfo.setProvince(jsonObject.getString("province"));
// 用户所在城市
weixinUserInfo.setCity(jsonObject.getString("city"));
// 用户头像
weixinUserInfo.setHeadImgUrl(jsonObject.getString("headimgurl"));
} catch (Exception e) {
int errorCode = jsonObject.getInteger("errcode");
String errorMsg = jsonObject.getString("errmsg");
System.out.println("由于"+errorCode +"错误码;错误信息为:"+errorMsg+";导致获取用户信息失败");
}
}
//获取用户信息后,可以存入数据库
User user = userDao.save(weixinUserInfo);
session.setAttribute(weixinUserInfo.getOpenId(),user);
return user;
}
除了响应关注事件,还可以对click等事件进行回复。
根据官方文档需要我们将回复的消息以xml的形式发送给微信后台。 下面我将用到的工具类贴在下面
XmlUtil类:
import com.hbrc.hbrcpublicserver.entity.wxmenu.TextMessage;
import com.thoughtworks.xstream.XStream;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @ClassName XmlUtil
* @Description TODO
* @Author Administrator
* @Date 2019/6/11 15:49
* @Version 1.0
**/
public class XmlUtil {
/*
* xml转map
*/
public static Map<String, String> xmlToMap(HttpServletRequest request) throws IOException, DocumentException {
HashMap<String, String> map = new HashMap<String,String>();
SAXReader reader = new SAXReader();
InputStream ins = request.getInputStream();
Document doc = reader.read(ins);
Element root = doc.getRootElement();
@SuppressWarnings("unchecked")
List<Element> list = (List<Element>)root.elements();
for(Element e:list){
map.put(e.getName(), e.getText());
}
ins.close();
return map;
}
/*
* 文本消息对象转xml
*/
public static String textMsgToxml(TextMessage textMessage){
XStream xstream = new XStream();
xstream.alias("xml", textMessage.getClass());
return xstream.toXML(textMessage);
}
}
MessageUtil类:
import com.hbrc.hbrcpublicserver.entity.User;
import com.hbrc.hbrcpublicserver.entity.wxmenu.TextMessage;
import com.hbrc.hbrcpublicserver.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.Date;
/**
* @ClassName MessageUtil
* @Description TODO
* @Author Administrator
* @Date 2019/6/11 16:13
* @Version 1.0
* @Description: 消息处理工具类
**/
@Component
public class MessageUtil {
public static final String MSGTYPE_EVENT = "event";//消息类型--事件
public static final String MESSAGE_SUBSCIBE = "subscribe";//消息事件类型--订阅事件
public static final String MESSAGE_UNSUBSCIBE = "unsubscribe";//消息事件类型--取消订阅事件
public static final String MESSAGE_TEXT = "text";//消息类型--文本消息
public static final String MESSAGE_CLICK = "CLICK";//消息类型-点击事件
@Autowired
private UserService userService;
public static MessageUtil messageUtil ;
@PostConstruct
public void init() {
messageUtil = this;
messageUtil.userService=this.userService;
//初使化时将已静态化的accessTokenOcrRepository实例化
}
/*
* 组装文本消息
*/
public String textMsg(String toUserName,String fromUserName,String content){
TextMessage text = new TextMessage();
text.setFromUserName(toUserName);
text.setToUserName(fromUserName);
text.setMsgType(MESSAGE_TEXT);
text.setCreateTime(new Date().getTime());
text.setContent(content);
return XmlUtil.textMsgToxml(text);
}
/*
* 响应订阅事件--回复文本消息
*/
public String subscribeForText(String toUserName,String fromUserName,HttpSession session){
//订阅成功后,根据openId获取用户信息 并注册。 fromUserName即为该用户的openid。
//因为订阅号没有获取用户信息授权的权限,code获取不到。无法用code换取openid。因此获取openid后直接调用获取用户信息的方法
// 获取基础刷新的接口访问凭证(目前还没明白为什么用authInfo.get("AccessToken");这里面的access_token就不行)
String accessToken = WeiXinUtils.getAccessToken().getToken();
try {
messageUtil.userService.getUserInfo(accessToken,fromUserName,session);
} catch (IOException e) {
e.printStackTrace();
}
return textMsg(toUserName, fromUserName, "欢迎关注,精彩内容不容错过!!!,详情请访问网址:http://www.mdline.cn");
}
/*
* 响应取消订阅事件
*/
public String unsubscribe(String fromUserName){
//取关后 删除数据库中的用户信息,后期如有关联删除失败再做处理。如有关联如 oneToMany 级联属性设置为Cascade.ALL。
messageUtil.userService.deleteByOpenid(fromUserName);
System.out.println("用户:"+ fromUserName +"取消关注~");
return "";
}
}
TextMessage类:
// 文本内容
private String Content;
// 消息id,64位整型
private String MsgId;
BaseMessage类:
// 开发者微信号
private String ToUserName;
//发送方帐号(一个OpenID)
private String FromUserName;
//消息创建时间 (整型)
private long CreateTime;
/**
* 消息类型
* text 文本消息
* image 图片消息
* voice 语音消息
* video 视频消息
* music 音乐消息
* event 事件推送
*/
private String MsgType;
private String Event;
这里只是文本消息,如果需要回复图文语音等消息可以自行封装。
到这里,订阅号获取用户信息的功能已经基本实现了。
另外多说一句,在接收微信发送给我们的xml包的方法里 我们定义了 String message = “success”;并在finally中返回给微信服务器。
理由参考官方文档入门指引。