前言:
关于监听公众号用户关注和取消关注的消息事件,微信官方文档给出的参考内容寥寥无几,具体如何配置url,官方文档也没有具体的说明,确实很坑,让人很难懂,而且网上关于配置微信消息事件接口的讲解资料很少,大多数只讲到验证token的url的配置,很少有讲到消息接口的url配置注意事项,很多开发者朋友也经常会卡到这里,通过加笔者微信请教笔者,本篇内容是笔者经过深入钻研实现的。能读到本篇干货的读者都是幸运的,经过精心整理,笔者今天把实现过程和完整案例无私分享给广大开发者朋友。
之前文章笔者已经实现了在测试公众号接入URL进行token验证,从本节起,开始利用微信官方提供的公众号开发文档来实现不同的交互案例。(注意:如果开发者还没有接入url进行token验证操作,请参考笔者之前的开发教程【springmvc开发微信公众号接口 微信公众号测试账号配置接口Token验证】。本案例演示效果如下:用户关注后公众号测试账号自动回复定制化的各种消息响应用户,并且可以根据用户发的消息进行智能回复。
1.打开公众账号测试网址http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login,扫码登陆测试公众号
2.我们打开微信公众号开发文档https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1445241432,选择左侧"消息管理"模块中的"接收普通消息",阅读微信官方开发文档。
文档中已经告诉我们,当普通微信用户向公众账号发送消息时,微信服务器会把该消息封装成XML数据包通过POST的方式发送到开发者填写的URL上。我们设置的URL仅仅只有一个,之前笔者分享的文章(【springmvc开发微信公众号接口 微信公众号测试账号配置接口Token验证】)中接入的url是用来做token验证的,就是微信服务器向开发者服务器发送GET请求过来,而现在是要用来做消息处理的,此时微信服务器向开发者服务器配置的url发送的是POST请求,因此我们只需要根据请求方式区分即可,配置的url和验证token配置的url保持一致即可,唯一不同的是,这次的请求接口路径上要用注解表明POST请求。详细见下面代码:
package com.chenyun.cloud.controller;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.dom4j.DocumentException;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import com.alibaba.fastjson.JSONObject;
import com.chenyun.cloud.utils.CheckoutUtil;
import com.chenyun.cloud.utils.MessageUtil;
import com.chenyun.cloud.utils.WeiXinUtil;
import com.chenyun.cloud.utils.XmlUtil;
/**
* 创建时间:2019年3月18日 下午3:35:33
* 项目名称:weixindev
* 类说明:微信开发之token验证以及各种消息处理
* @author guobinhui
* @since JDK 1.8.0_51
*/
@Controller
public class WeixinMsgController {
public static String URL = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN";
/**
* 微信消息接收和token验证
* @param request
* @param response
* @throws IOException
*/
@RequestMapping(value="/msg/checkToken",method=RequestMethod.GET)
public void weChat(HttpServletRequest request, HttpServletResponse response) throws IOException {
boolean isGet = request.getMethod().toLowerCase().equals("get");
if (isGet) {
// 微信加密签名
String signature = request.getParameter("signature");
// 时间戳
String timestamp = request.getParameter("timestamp");
// 随机数
String nonce = request.getParameter("nonce");
// 随机字符串
String echostr = request.getParameter("echostr");
// 通过检验signature对请求进行校验,若校验成功则原样返回echostr,表示接入成功,否则接入失败
if (signature != null && CheckoutUtil.checkSignature(signature, timestamp, nonce)) {
try {
boolean flag = CheckoutUtil.checkSignature(signature, timestamp, nonce);
System.out.println(flag);
PrintWriter print = response.getWriter();
print.write(echostr);
System.out.println(echostr);
print.flush();
print.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@RequestMapping(value="/msg/checkToken",method=RequestMethod.POST)
@ResponseBody
public String responseEvent(HttpServletRequest req, HttpServletResponse resp)throws IOException {
req.setCharacterEncoding("UTF-8");
resp.setCharacterEncoding("UTF-8");
String message = "success";
try {
//把微信返回的xml信息转义成map
Map map = XmlUtil.xmlToMap(req);
System.out.println("微信接收到的消息为:"+map.toString());
String fromUserName = map.get("FromUserName");//消息来源用户标识
String toUserName = map.get("ToUserName");//消息目的用户标识
String msgType = map.get("MsgType");//消息类型(event或者text)
System.out.println("消息来源于:"+fromUserName);
System.out.println("openId:"+toUserName);
System.out.println("消息类型为:"+msgType);
String eventType = map.get("Event");//事件类型
String nickName = getUserNickName(fromUserName);
if(MessageUtil.MSGTYPE_EVENT.equals(msgType)){//如果为事件类型
if(MessageUtil.SUBSCIBE_EVENT.equals(eventType)){//处理订阅事件
String content = "欢迎关注,这是一个公众号测试账号,您可以回复任意消息测试,开发者郭先生,18629374628";
String msg = "@"+ nickName + "," +content;
System.out.println("事件类型为:"+","+eventType);
message = MessageUtil.subscribeForText(toUserName, fromUserName,msg);
}else if(MessageUtil.UNSUBSCIBE_EVENT.equals(eventType)){//处理取消订阅事件
System.out.println("事件类型为:"+eventType);
message = MessageUtil.unsubscribe(toUserName, fromUserName);
}
}else {
//微信消息分为事件推送消息和普通消息,非事件即为普通消息类型
switch (msgType) {
case "text":{//文本消息
String content = map.get("Content");//用户发的消息内容
content = "您发的消息内容是:"+content+",如需帮助,请联系郭先生,18629374628";
message = MessageUtil.replyMsg(toUserName, fromUserName,content,"text");
break;
}
case "image":{//图片消息
String content = "您发的消息内容是图片,如需帮助,请联系郭先生,18629374628";
message = MessageUtil.replyMsg(toUserName, fromUserName,content,"text");
break;
}
default:{//其他类型的普通消息
break;
}
}
}
} catch (DocumentException e) {
e.printStackTrace();
}
System.out.println("关注微信公众号自动回复的消息内容为:"+message);
return message;
}
public static String getUserNickName(String openId) {
String nickName = null;
try {
Map map = WeiXinUtil.cacheTokenAndTicket();
String token = (String)map.get("access_token");
String url = URL.replace("OPENID", openId).replace("ACCESS_TOKEN", token);
JSONObject obj = WeiXinUtil.HttpGet(url);
nickName = (String)obj.get("nickname");
} catch (IOException e) {
e.printStackTrace();
}
return nickName;
}
}
次处access_token缓存7200秒是用笔者写的一个工具类实现的,这个工具类也一并分享给大家,主要思路就是通过IO流把生成的access_token写到项目所在的文件中,每隔7200秒重新生成access_token覆盖原有的access_token,当然也可以用ehcache等缓存插件实现,那么所有的需要用到access_token的请求,取到的access_token都是实时的最新的。直接看代码:
public static Map cacheTokenAndTicket() throws IOException {
Gson gson = new Gson();
Map map = new HashMap ();
String token = null;
String ticket = null;
JSONObject tokenObj = null; //需要获取的access_token对象;
JSONObject ticketObj = null;//需要获取的jsapi_ticket对象;
String filePath = System.getProperty("user.dir")+"/src/main/resources/token.txt";
File file = new File(filePath);//Access_token保存的位置
if (!file.exists())
file.createNewFile();
// 如果文件大小等于0,说明第一次使用,存入Access_token
if (file.length() == 0) {
tokenObj = WeiXinUtil.getToken();
token = (String)tokenObj.get("access_token");
FileOutputStream fos = new FileOutputStream(filePath, false);// 不允许追加
tokenObj.put("expires_in",System.currentTimeMillis()/1000+"");
String json = gson.toJson(tokenObj);
fos.write(json.getBytes());
fos.close();
}else {
//读取文件内容
@SuppressWarnings("resource")
FileInputStream fis = new FileInputStream(file);
byte[] b = new byte[2048];
int len = fis.read(b);
String jsonAccess_token = new String(b, 0, len);// 读取到的文件内容
JSONObject access_token = gson.fromJson(jsonAccess_token,JSONObject.class);
if (access_token.get("expires_in") != null) {
String lastSaveTime = (String)access_token.get("expires_in");
long nowTime = System.currentTimeMillis()/1000;
long remianTime = nowTime - Long.valueOf(lastSaveTime);
if (remianTime < WeixinConstant.EXPIRESIN_TIME) {
JSONObject access = gson.fromJson(jsonAccess_token,JSONObject.class);
token = (String)access.get("access_token");
} else {
tokenObj = WeiXinUtil.getToken();
FileOutputStream fos = new FileOutputStream(file, false);// 不允许追加
tokenObj.put("expires_in",System.currentTimeMillis()/1000+"");
String json = gson.toJson(tokenObj);
fos.write((json).getBytes());
fos.close();
}
}
}
map.put("access_token",token);
map.put("jsapi_ticket",ticket);
return map;
}
点击这里免费获取源码 :https://download.csdn.net/download/guobinhui/11270776
更多JavaEE资料请关注下面公众号,欢迎广大开发者朋友一起交流。笔者电话(微信):18629374628
具体功能体验可以扫左边的公众号二维码体验