最全面的微信公众号开发教程:基于Springboot实现关注/取消关注事件监听

gongzhonghao 的头像

想尝试开发微信公众号,却对官方手册感到困惑?网上的资料也不多,许多重要设置都没讲明白。别担心,我现在就提供一份非常详细的教程给你!

痛点大揭秘

不少从事微信公众号开发的朋友都有这样的体会,微信官方的文档在这部分关于用户关注与取消关注消息事件的说明非常简略。特别是关于如何设置URL的部分,文档表述模糊,让人感到非常不满。而且,关于配置消息事件接口的资料在网上并不多见。很多人只提到了token验证的URL设置,而对于消息接口的URL配置注意事项,却鲜有涉及。因此,许多开发者在这一环节遇到了难题,不得不四处寻求帮助。

前期准备

在使用微信官方文档进行各种交互案例实践之前,需要先做好一系列前期准备工作。我之前有撰写过一篇关于如何测试公众号接入URL进行token验证的文章,其中提到了【springmvc开发微信公众号接口 微信公众号测试账号配置接口Token验证】这一教程。若您尚未进行过相关操作,请先查阅该教程。只有完成这一步骤,后续工作才能顺利进行。

公众号功能开发思路

演示效果展示

完成前期准备,咱们现在可以观察一下这个案例的展示效果。一旦用户关注了我们的公众号测试账号,系统便会自动发送个性化的信息。此外,公众号还能根据用户发送的内容智能地给出回应,仿佛真的在与真人对话,为用户提供优质的互动体验。正因如此,公众号能够吸引更多用户,有效提升用户的活跃度。

文档查阅学习

公众号功能开发思路

打开微信公众号的开发指南,进入“消息管理”区域,选择“接收普通消息”选项,并认真查阅官方的指导文件。根据指南的说明,当普通用户向公众号发送信息时,微信的服务器会将信息打包成XML格式的数据,然后以POST请求的形式发送至开发者设定的URL地址。

URL配置要点

我们只设置了一个URL,之前这个URL用于token验证,那时微信服务器发送的是GET请求。现在,它被用于处理消息,微信服务器发送的是POST请求。因此,我们只需根据请求方式来区分处理。URL和验证token时保持不变,但请求接口路径需要用注解标明是POST请求。通过这种方式,我们可以正确处理不同类型的请求,确保公众号的正常运行。

access_token缓存实现

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。当然,也可以选择使用ehcache等缓存插件。这样一来,所有依赖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;
	}

gongzhonghao 的头像

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

网站建设杨老师

1.5M Followers

关注“网站建设杨老师”,获取免费专业咨询服务,开启您的数字化之旅!