微信公众号开发系列教程(四):监听关注/取消关注事件 消息接收与响应处理

10万+IT都在关注,史上最全面的微信公众号开发系列教程:基于Springboot开发公众号关注/取消关注事件

前言:

关于监听公众号用户关注和取消关注的消息事件,微信官方文档给出的参考内容寥寥无几,具体如何配置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<String, String> 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 <String,Object> cacheTokenAndTicket() throws IOException {
	  Gson gson = new Gson();
      Map <String,Object> map = new HashMap <String,Object> ();
      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

具体功能体验可以扫左边的公众号二维码体验 

评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值