扫码关注公众号登录系统

微信开发--扫码关注公众号登录系统

  • 前言
  • 准备阶段
          • NATAPP
          • 测试账号
          • 工具代码
  • 微信 API 调研阶段
      • 步骤1:注册账号(如果使用测试账号可跳过)
      • 步骤2:了解微信扫码机制
      • 步骤3:了解微信消息类型
        • 带参数二维码
  • 编码对接微信服务器阶段
    • 实现思路
    • 创建项目
      • access_token
    • 测试账号
      • 配置接口配置信息
    • 正式账号
    • 服务器配置
      • 创建 GET 接口验证签名
        • 获取ticket并跳转至二维码界面
        • 创建 POST 接口接收消息/事件推送
  • 总结

前言

最近公司的客户要求在登录时加上一个流程,就是在输入账号密码之后要求用户关注他们的微信公众号才能进入系统,作为职场新人的我从来没有接触过微信 API ,经历3天的调研和2天的代码终于把功能做了出来。在这里记录一下,顺便和大家分享一下我踩过的坑。由于水平有限,如果有什么错误欢迎指出,但请不要言语谩骂。如果我帮助到了同样是第一次接触微信API的你,希望不要吝啬一个赞,谢谢!!!
开篇之前介绍下我设计的登录流程
扫码关注公众号登录系统_第1张图片

准备阶段

在注册微信公众平台的开发者账号之前,您需要准备如下工具

NATAPP

无论是公众平台的正式账号,还是用来开发的测试账号,微信都要求开发者提供一个公网可访问的域名。如果您没有域名并且不打算在功能完成之前购买域名,可以考虑使用NATAPP。这里提供一个教程教您配置macOS下载及配置教程、windows下载及配置教程

测试账号

在项目上线之前,公司可能不会提供正式账号。微信为我们提供了一个开通了所有接口权限(除了支付)的测试账号,申请地址:戳我申请开发者测试账号

工具代码

在用到这些代码之前,请先把代码复制到工程中,不用管具体实现,都是一些死代码没必要理解。

  1. java 发送 http 请求代码
public class NetWorkUtil {
    public static String httpURLConnectionPOST(String postUrl, QrCodeParam param) {
        try {
            URL url = new URL(postUrl);
            // 1. 将url 以 open方法返回的urlConnection
            // 连接强转为HttpURLConnection连接.此时cnnection只是为一个连接对象,待连接中
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            // 2. 设置连接输出流为true,默认false (post 请求是以流的方式隐式的传递参数)
            connection.setDoOutput(true);
            // 3. 设置连接输入流为true
            connection.setDoInput(true);
            // 4.设置请求方式为post
            connection.setRequestMethod("POST");
            // 5.post请求缓存设为false
            connection.setUseCaches(false);
            /*
             * 6.设置请求头里面的各个属性 (以下为设置内容的类型,设置为经过urlEncoded编码过的from参数)
             * application/xml:xml数据 ,application/json:json对象
             * text/html:表单数据
             */
            connection.setRequestProperty("Content-Type", "application/json;charset=utf-8");
            // 7.建立连接
            // (请求未开始,直到connection.getInputStream()方法调用时才发起,以上各个参数设置需在此方法之前进行)
            connection.connect();

            // 8.创建输入输出流,用于往连接里面输出携带的参数,(输出内容为?后面的内容)
            DataOutputStream dataout = new DataOutputStream(connection.getOutputStream());
            // 9.入参:json格式
            String jsonStr = JSON.toJSONString(param);
            // 10.将参数输出到连接
            dataout.writeBytes(jsonStr);
            // 输出完成后刷新并关闭流
            dataout.flush();
            dataout.close(); // 重要且易忽略步骤 (关闭流,切记!)
            // System.out.println("响应code:"+connection.getResponseCode());
            // 连接发起请求,处理服务器响应 (从连接获取到输入流并包装为bufferedReader)
            BufferedReader bf = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
            String line;
            StringBuilder sb = new StringBuilder(); // 用来存储响应数据
            // 循环读取流,若不到结尾处
            while ((line = bf.readLine()) != null) {
                sb.append(line);//若要换行:sb.append(line).append(System.getProperty("line.separator"));
            }
            bf.close(); // 重要且易忽略步骤 (关闭流,切记!)
            connection.disconnect(); // 销毁连接
            System.err.println(sb.toString());
            return sb.toString();
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("请求失败:"+e.getMessage());
        }
        return "";
    }
    
    public static String httpURLConnectionGET(String getURL){
       try{
           URL url = new URL(getURL);
           HttpURLConnection connection = (HttpURLConnection) url.openConnection();
           connection.setDoOutput(true); // 设置该连接是可以输出的
           connection.setRequestMethod("GET"); // 设置请求方式
           connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
           BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream(), "utf-8"));
           String line = null;
           StringBuilder result = new StringBuilder();
           while ((line = br.readLine()) != null) { // 读取数据
               result.append(line);
           }
           connection.disconnect();
           return result.toString();
       }catch (Exception e){
           e.printStackTrace();
       }
       return null;
    }
}
  1. 将微信服务器推送的XML形式的JSON转换成Map(关于微信服务器推送的消息格式请查阅这个教程)
public class MessageUtil {
    public static Map<String, String> parseXml(HttpServletRequest req){
        Map<String, String> map = new HashMap<>();
        try(
                InputStream inputStream = req.getInputStream();
         ){
            // 初始化 Dom4j核心 对象
            SAXReader reader = new SAXReader();
            Document document = reader.read(inputStream);
            // 获取根节点
            Element rootElement = document.getRootElement();
            // 获取所有节点
            List<Element> elements = rootElement.elements();
            for (Element element : elements) {
                // 节点名做为 key, 文本节点做为值
                map.put(element.getName(), element.getText());
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return map;
    }
    /**
     * 扩展xstream使其支持CDATA
     */
    private static XStream xstream = new XStream(new XppDriver() {
        public HierarchicalStreamWriter createWriter(Writer out) {
            return new PrettyPrintWriter(out) {
                // 对所有xml节点的转换都增加CDATA标记
                boolean cdata = true;

                @SuppressWarnings("unchecked")
                public void startNode(String name, Class clazz) {
                    super.startNode(name, clazz);
                }

                protected void writeText(QuickWriter writer, String text) {
                    if (cdata) {
                        writer.write(");
                        writer.write(text);
                        writer.write("]]>");
                    } else {
                        writer.write(text);
                    }
                }
            };
        }
    });

    /**
     * 文本消息对象转换成xml
     *
     * @param textMessage 文本消息对象
     * @return xml
     */
    public static String messageToXml(TextMessage textMessage) {
        xstream.alias("xml", textMessage.getClass());
        return xstream.toXML(textMessage);
    }
}

  1. 对微信发来的验证参数进行排序和加密的代码
public class EncodingUtil {
    public static String sort(String token, String timestamp, String nonce){
        String[] str = {token, timestamp, nonce};
        Arrays.sort(str);
        StringBuilder builder = new StringBuilder();
        for (String s : str) {
            builder.append(s);
        }
        return builder.toString();
    }

    public static String shal(String str) {
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-1");
            digest.update(str.getBytes());
            byte messageDigest[] = digest.digest();

            StringBuffer hexString = new StringBuffer();
            // 字节数组转换为 十六进制 数
            for (int i = 0; i < messageDigest.length; i++) {
                String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);
                if (shaHex.length() < 2) {
                    hexString.append(0);
                }
                hexString.append(shaHex);
            }
            return hexString.toString();

        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return "";
    }
}

微信 API 调研阶段

步骤1:注册账号(如果使用测试账号可跳过)

第一 步当然是在微信公众平台注册一个账号啦,但是在注册之前,希望你了解一下订阅号服务号的接口权限说明,以免自己注册的账号不能满足功能要求。
如果已经拥有公众平台的账号了,一定要注意下您注册的是订阅号/服务号而不是小程序,本人就是因为半年之前注册了一个小程序的开发者账号(只是注册一个玩玩,从来没用过),然后第一天登录公众平台后就只显示小程序的接口不显示订阅号/服务号的接口。导致我按照微信官方文档的公众号文档中的说明找半天也找不到对应的接口,气得我问候老马的mother(无能咆哮)。浪费我一天的时间(真是蠢哭了)。

步骤2:了解微信扫码机制

经过本人实验,对于已关注的用户,微信只会在该用户扫描带参数二维码时才会推送给服务器消息。对于未关注用户,在扫码后并不会直接推送给服务器事件消息,只有点击了“关注公众号”才会推送关注事件消息

步骤3:了解微信消息类型

微信的消息类型有如下几种格式

  1. 文本消息
  2. 图片消息
  3. 语音消息
  4. 视频消息
  5. 小视频消息
  6. 地理位置消息
  7. 链接消息
  8. 关注/取消关注事件
  9. 扫描带参数二维码事件
  10. ······

我们实现的是登录后扫码关注公众号进入系统,最应该关心的就是8和9这两种消息类型

带参数二维码

何为带参数二维码?可以看下微信官方的解释,微信开发文档/生成带参数二维码

为了满足用户渠道推广分析和用户帐号绑定等场景的需要,公众平台提供了生成带参数二维码的接口。使用该接口可以获得多个带不同场景值的二维码,用户扫描后,公众号可以接收到事件推送。

举个例子,商家为了推广自己的产品,经常会实行奖励机制:谁推送的我就给谁个回扣。商家如何知道一个新的用户是谁推荐的呢?在微信中,商户就是通过带参数二维码来获知这个新的用户是谁推荐来的了。

带参数二维码可以推送以下两种事件

  1. 如果用户还未关注公众号,则用户可以关注公众号,关注后微信会将带场景值关注事件推送给开发者。
  2. 果用户已经关注公众号,则微信会将带场景值扫描事件推送给开发者。

实现登录功能我们不需要关心二维码的参数,我们使用带参数二维码的主要目的是为了接收已经关注公众号用户的扫码事件,加上关注,我们就能接收两种用户(关注的用户和未关注的用户)的扫码事件了。

编码对接微信服务器阶段

实现思路

在用户输入完账号密码之后跳转到我们的带参数二维码界面,跳转到该界面时,开启一个定时任务,不断的向后端某个接口询问该用户是否已经扫码登录(ajax轮询)。由于每个二维码的ticket都是唯一的,所以可以对应一个客户端用户。被轮询的接口会返回3种状态:

  1. 用户已经扫码并关注,请跳转到系统主页(跳转页面)
  2. 用户还没有进行扫码,请继续等待(无为而坐)
  3. 二维码过期,请重新获取(刷新当前页面)

创建项目

推荐使用 springboot 创建项目,这能够省去配置项目的时间(如何创建 springboot 项目请自行百度)。注意,项目启动端口请设置成 80(http 默认端口) 或者 443(https 默认端口),这是微信官方要求的,如果想用其他端口可以使用Nginx 反向代理(同样的,Nginx使用方法请自行百度)
需要引入如下依赖

		
        
            org.springframework.boot
            spring-boot-starter-thymeleaf
        
        
            org.springframework.boot
            spring-boot-starter-web
        
        
        
            dom4j
            dom4j
            1.6.1
        
        
            com.thoughtworks.xstream
            xstream
            1.4.10
        
        
            com.alibaba
            fastjson
            1.2.58
        
		
        
            org.springframework.boot
            spring-boot-starter-cache
        

        
            org.springframework.boot
            spring-boot-starter-data-redis
        

        
            org.springframework.boot
            spring-boot-starter-web
        
        
            org.projectlombok
            lombok
            true
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        

access_token

请先移步到微信官方文档/获取AccessToken了解其重要性
如果您仔细阅读文档,一定会发现这么一句话:access_token的有效期目前为2个小时,需定时刷新。这个定时刷新,我是通过一个线程,每隔 2小时 - 20 秒刷新一次 access_token,提前20秒防止意外情况发生阻塞 access_token的刷新。并且该线程在 springboot 项目启动后立即启动。具体代码实现如下


@SpringBootApplication
//开启缓存
@EnableCaching
public class DemoApplication {
    @Autowired
    private WeChatBasicInfo basicInfo;
    // 自己的测试号
    private static String appID = "你的appID";
    private static String appsecret = "你的appsecret";
    // 这个类的定义在下面
    public static AccessToken accessToken = null;
	// 请求 access_token 的接口
    private static String uri = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s";

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
        poll();
    }
	// 开启获取 access_token 的线程
    private static void poll() {
        new Thread(() -> {
            synchronized (DemoApplication.class) {
                try {
                    System.out.println("=----------------- 获取accesstoken");
                    while (true) {
                        uri = String.format(uri, appID, appsecret);
                        URL url = new URL(uri);
                        HttpURLConnection connection = (HttpURLConnection) url.openConnection();

                        connection.setDoOutput(true); // 设置该连接是可以输出的
                        connection.setRequestMethod("GET"); // 设置请求方式
                        connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");

                        BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream(), "utf-8"));
                        String line = null;
                        StringBuilder result = new StringBuilder();
                        while ((line = br.readLine()) != null) { // 读取数据
                            result.append(line);
                        }
                        connection.disconnect();
                        String jsonStr = result.toString();
                        accessToken = JSON.parseObject(jsonStr, AccessToken.class);
                        // 判断请求是否成功
                        if (accessToken.getErrcode() == null || accessToken.getErrcode().isEmpty()) {
                            System.out.println("获取 token 成功 token =  " + accessToken.getAccess_token());
                            // 请求成功等待两小时
                            Thread.sleep(7000 * 1000);
                        } else {
                            System.out.printf("获取 token 失败,请重新获取。错误码 : %s\t请查阅微信公众平台返回码(https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html)", accessToken.getErrcode());
                            // 3秒后重试一次
                            Thread.sleep(1000 * 3);
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

        }).start();
    }
}

@Data
public class AccessToken {
    private String access_token;
    private String expires_in; // 过期时间
    private String errcode; // 如果请求出错才会有值
    private String errmsg;// 如果请求出错才会有值
}

测试账号

如果您注册好测试账号后,进入测试账号首页就会看到测试号信息了。
在这里插入图片描述
其中 appID相当于用户名,appsecret相当于密码。这两个属性用来换区**access_token**,非常重要,推荐在项目中已常量或者放在配置文件中。

配置接口配置信息

扫码关注公众号登录系统_第2张图片
接口配置信息有两个用途

  1. 微信服务器校验签名( GET 请求)
  2. 接收微信服务器推送的事件和用户发来的消息( POST请求)(这是官方文档没有详细说明的,坑的我两天都无法接收关注/取消事件和扫码事件

解释下参数

  1. URL 是你的项目接口,用来校验签名和接收用户信息/事件推送请求的地址
  2. 随便填,用来校验签名

    PS : URL就填你用NATAPP进行内网穿透显示的网址 + 你的项目地址即可
在点击提交按钮后,会向我们填写的 URL 发送一个请求,请求中一共有四个参数,官方文档解释传送门
扫码关注公众号登录系统_第3张图片
前三个没什么好说的,主要是最后一个。如果通过签名校验通过,原样返回 echostr 。如果没通过什么也不用干。

正式账号

前提条件:首先要确定你注册的账号是服务号 才能查看到我下面介绍的配置项,同时,你必须是通过微信认证的服务号才能生成带参数的二维码(据说300RMB)。

进行配置:鼠标滚到最下面,可以在左侧导航栏看见开发选项,点击基本配置
可以看到正式号也有 AppID 和 AppSecret
扫码关注公众号登录系统_第4张图片
不同的是正式号多了一个 IP 白名单,这个 IP 白名单需要配置本机 公网IP 。(注意,在终端/cmd获取的 IP 不是公网IP)打开百度搜索本机IP 即可获得本机的 公网IP。

服务器配置

服务器配置和测试号的接口配置信息一样,GET请求用来校验签名,POST请求用来处理用户信息/事件推送。
扫码关注公众号登录系统_第5张图片

创建 GET 接口验证签名

	private static final String TOKEN = "你填写的TOKEN";
	/**
     * 微信服务器校验签名
     * @param req
     * @param resp
     * @throws Exception
     */
    @RequestMapping(value = "/wechatLogin/test",method = RequestMethod.GET)
    public void login(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        System.out.println("-----开始校验签名-----");
        // 接收微信服务器发送请求时传递过来的参数
        String signature = req.getParameter("signature");
        String timestamp = req.getParameter("timestamp");
        String nonce = req.getParameter("nonce"); //随机数
        String echostr = req.getParameter("echostr");//随机字符串
        // 将token、timestamp、nonce三个参数进行字典序排序并拼接为一个字符串
        String sortStr = EncodingUtil.sort(TOKEN,timestamp, nonce);
        // 字符串进行shal加密
        String mySignature = EncodingUtil.shal(sortStr);
        // 校验微信服务器传递过来的签名 和  加密后的字符串是否一致, 若一致则签名通过
        if (!"".equals(signature) && !"".equals(mySignature) && signature.equals(mySignature)) {
            System.out.println("-----签名校验通过-----");
            resp.getWriter().write(echostr);
        } else {
            System.out.println("-----校验签名失败-----");
        }
    }

启动项目,点击提交按钮,如果一切顺利你将会看到
扫码关注公众号登录系统_第6张图片
如果配置失败请打断点查看四个参数和你进行加密之后的结果。
下面的其他配置,例如JS接口安全域名,我也不知道它是干嘛的,就不乱写坑大家了,反正我没用上它。

获取ticket并跳转至二维码界面

前面在启动类中开启了一个线程,每隔2小时重新获取一次 access_token 并赋值给一个 static变量,这样我们就可以在其他类中获取这个 access_token 了。

@Autowired
    private RedisService redisService;
    /**
     * 二维码参数
     */
    @Autowired
    private QrCodeParam param;

    /**
     * 获取二维码 ticket
     * @param model
     * @param session
     * @return
     */
    @RequestMapping(value = "/testToken", method = RequestMethod.GET)
    public String testQrCode(Model model, HttpSession session) {
        // 用token换取带参数二维码的 ticket
        String access_token = DemoApplication.accessToken.getAccess_token();
        String url = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=" + access_token;
        // 获取 ticket 的JSON格式
        String result = NetWorkUtil.httpURLConnectionPOST(url, param);
        // 将JSON转换成Map
        Map map = JSON.parseObject(result, Map.class);
        // 获取 ticket
        String ticket = map.get("ticket").toString();
        model.addAttribute("ticket", result);
        Long aLong = Long.valueOf(param.getExpire_seconds());
        // 用 ticket 做为一个客户端的key(redis 的 key),值为 null ,只有当用户扫码并关注后才存值
        redisService.set(ticket, null, aLong);
        return "qrTest.html";
    }

下面是 qrTest.html中的定义,ajax 轮询的代码也在其中


<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Documenttitle>
head>
<body>
    <h3 >token = h3>
    <img  alt="场景参数二维码" id="qrCode">
    <script src="https://cdn.bootcss.com/jquery/2.1.2/jquery.js">script>
    <script th:inline="javascript">
        // 获取 ticket 并用来获取二维码,这是  thymeleaf 的取值方式,如果你用的 jsp 或者其他模板请注意更换
        var ticket = JSON.parse([[${ticket}]]);
        $('#qrCode').attr('src','https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket='+ticket.ticket);
        setInterval(function(){
            console.log('轮询一次');
            $.ajax({
                type : 'post',
                dataType : 'json',
                data : {
                    "ticket":ticket.ticket,
                    "expireSeconds" : ticket.expire_seconds
                },
                url : '/qwxLogin/validQrCode',
                success : function(response){
                    console.log('请求成功',response);
                    if(response.wait){
						// 无为而坐
                    }else if(response.reload){
                        alert('刷新');
                        // 重新获取二维码
                        window.location.reload();
                    }else if(response.scaned){
                        alert('扫描完毕');
                        // 一个测试页面,可以理解为系统主页
                        window.location.href = "/focus/hello";
                    }
                },
                error : function(error){
                    console.log('请求失败',error);
                }
            })
            // 每隔一秒轮询一次
        },1000 * 1)
    script>
body>
html>
@Component
@Data
@PropertySource("classpath:wechat.properties")
public class QrCodeParam {
    /**
     * 该二维码有效时间,以秒为单位。 最大不超过2592000(即30天),此字段如果不填,则默认有效期为30秒。
     */
    @Value("${qr.expire_seconds}")
    private String expire_seconds;
    /**
     * 二维码类型,
     * QR_SCENE为临时的整型参数值,
     * QR_STR_SCENE为临时的字符串参数值,
     * QR_LIMIT_SCENE为永久的整型参数值,
     * QR_LIMIT_STR_SCENE为永久的字符串参数值
     */
    @Value("${qr.action_name}")
    private String action_name;

    /**
     * 二维码场景参数
     */
    private Map<String,Object> action_info;

    /**
     * sessionID,用来区分二维码是哪个客户端的
     */
    private String sessionID;
}

这里我把带参数的二维码需要设置的一些参数定义到了配置文件wechat.properties中,方便以后更改。(如果你不太了解这些字段,请一定要仔细看一下 官方文档)

创建 POST 接口接收消息/事件推送

在测试账号的接口配置信息 的URL或者正式号的服务器配置中的URL既是接收消息/事件推送的接口地址,在Controller中创建一个POST请求即可。
微信服务器会将用户信息/事件推送以 XML 格式的字符串传递到我们配置的接口中,查看所有格式请移步微信公众平台->消息管理。这里只介绍登录需要的 关注/取关、扫描带参数的二维码事件推送的消息。

  • 关注/取关事件推送 XML 格式
<xml>
  <ToUserName>ToUserName>
  <FromUserName>FromUserName>
  <CreateTime>123456789CreateTime>
  <MsgType>MsgType>
  <Event>Event>
xml>
  • 扫描带参数二维码
    • 用户未关注时,进行关注后的事件推送
<xml>
  <ToUserName>ToUserName>
  <FromUserName>FromUserName>
  <CreateTime>123456789CreateTime>
  <MsgType>MsgType>
  <Event>Event>
  <EventKey>EventKey>
  <Ticket>Ticket>
xml>

扫码关注公众号登录系统_第7张图片
* 用户已关注时的事件推送

<xml>
  <ToUserName>ToUserName>
  <FromUserName>FromUserName>
  <CreateTime>123456789CreateTime>
  <MsgType>MsgType>
  <Event>Event>
  <EventKey>EventKey>
  <Ticket>Ticket>
xml> 

扫码关注公众号登录系统_第8张图片
对于 XML 形式的字符串,可以使用上面提供的MessageUtil中的parseXML方法进行解析,返回一个Map。

接收微信服务器推送的 事件/用户信息 的POST接口如下

/**
     * 当公众号接收消息时,调用该接口,和上面的验证签名的 mapping 相同,请求方式不同
     * @param req
     * @param res
     * @throws Exception
     */
    @RequestMapping(path = "/wechatLogin/test", method = RequestMethod.POST)
    public void receivesMessage(HttpServletRequest req, HttpServletResponse res) throws Exception{
        // 设置编码,否则返回给用户信息时可能会乱码
        res.setCharacterEncoding("UTF-8");
        // 解析微信服务器传来的 XML 格式字符串
        Map<String, String> stringMap = MessageUtil.parseXml(req);
        // 获取消息类型
        String msgType = stringMap.get("MsgType");
        // 如果是事件推送
        if(MessageTypeEnum.REQ_MESSAGE_TYPE_EVENT.getTypeName().equals(msgType)){
            // 如果用户已经关注过公众号,就是 SCAN 事件,或者是用户的关注事件
            if(EventTypeEnum.AFTER_FOCUS_ON.getEvent().equals(stringMap.get("Event")) || EventTypeEnum.BEFORE_FOCUS_ON.getEvent().equals(stringMap.get("Event"))){
                System.err.println("关注或者扫码事件。 xml =  " + req.getSession().getId());
                // 注意,如果要回复用户消息,FromUserName 和 ToUserName 要调换顺序
                String toUser = stringMap.get("FromUserName");
                String fromUser = stringMap.get("ToUserName");
                // 获取 ticket
                String ticket = stringMap.get("Ticket");
                // 将用户扫描的二维码的 Ticket作为 key,值为扫码用户的openId. redisService 代码会在后面贴出。
                redisService.set(ticket,toUser);
                // 临时返回信息 xml 字符串             这里有个%s                               这里有个%s                                                    
                String reply = "12345678";
                String format = String.format(reply, toUser, fromUser);
                res.getWriter().write(format);
            }
            // 如果用户发来的文本事件,当然还可能有其他事件。请自行添加其他事件
        }else {
            System.err.println("文本事件");
            String toUser = stringMap.get("FromUserName");
            String fromUser = stringMap.get("ToUserName");
            String reply = "12345678";
            String format = String.format(reply, toUser, fromUser);
            res.getWriter().write(format);
        }
    }

可以看到接收用户的消息/事件推送后给用户返回了一条提示消息,和接收消息一样回复消息也要是微信指定的 XML 格式,具体格式请移步微信公众平台/被动回复用户消息。具体的消息封装成 XML 格式并返回给微信服务器的方法,请查看 这篇教程。

总结

作为一名刚刚参加工作的初级开发攻城狮,当学习了新的知识之后做一个总结是个很好的习惯。记录的同时也能发现之前自己犯的错有多傻。还有,以前百度别人的教程,从来都是 command + ccommand+vcommand + w完事,从来没想过要写这么久。

你可能感兴趣的:(微信开发,微信登录,微信公众平台,java,微信开发)