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

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

前言

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

准备阶段

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

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. 设置连接输入流为trueconnection.setDoInput(true);// 4.设置请求方式为postconnection.setRequestMethod("POST");// 5.post请求缓存设为falseconnection.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("<![CDATA[");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使用方法请自行百度)
需要引入如下依赖

     <!-- 我的项目使用了 thymeleaf 模板,如果你用的是 jsp 或者其他模板,在页面中的取值方式注意更换一下 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- 用来解析微信服务器推送的事件和用户发来的消息 --><dependency><groupId>dom4j</groupId><artifactId>dom4j</artifactId><version>1.6.1</version></dependency><dependency><groupId>com.thoughtworks.xstream</groupId><artifactId>xstream</artifactId><version>1.4.10</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.58</version></dependency><!--  springboot 基础配置项--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency>

access_token

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


@SpringBootApplication
//开启缓存
@EnableCaching
public class DemoApplication {@Autowiredprivate 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**,非常重要,推荐在项目中已常量或者放在配置文件中。

配置接口配置信息


接口配置信息有两个用途

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

解释下参数

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

PS : URL就填你用NATAPP进行内网穿透显示的网址 + 你的项目地址即可
在点击提交按钮后,会向我们填写的 URL 发送一个请求,请求中一共有四个参数,官方文档解释传送门

前三个没什么好说的,主要是最后一个。如果通过签名校验通过,原样返回 echostr 。如果没通过什么也不用干。

正式账号

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

进行配置:鼠标滚到最下面,可以在左侧导航栏看见开发选项,点击基本配置
可以看到正式号也有 AppID 和 AppSecret

不同的是正式号多了一个 IP 白名单,这个 IP 白名单需要配置本机 公网IP 。(注意,在终端/cmd获取的 IP 不是公网IP)打开百度搜索本机IP 即可获得本机的 公网IP。

服务器配置

服务器配置和测试号的接口配置信息一样,GET请求用来校验签名,POST请求用来处理用户信息/事件推送。

创建 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("-----校验签名失败-----");}}

启动项目,点击提交按钮,如果一切顺利你将会看到

如果配置失败请打断点查看四个参数和你进行加密之后的结果。
下面的其他配置,例如JS接口安全域名,我也不知道它是干嘛的,就不乱写坑大家了,反正我没用上它。

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

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

@Autowiredprivate RedisService redisService;/*** 二维码参数*/@Autowiredprivate QrCodeParam param;/*** 获取二维码 ticket* @param model* @param session* @return*/@RequestMapping(value = "/testToken", method = RequestMethod.GET)public String testQrCode(Model model, HttpSession session) {// 用token换取带参数二维码的 ticketString 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转换成MapMap map = JSON.parseObject(result, Map.class);// 获取 ticketString 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 轮询的代码也在其中

<!DOCTYPE html>
<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>Document</title>
</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><![CDATA[toUser]]></ToUserName><FromUserName><![CDATA[FromUser]]></FromUserName><CreateTime>123456789</CreateTime><MsgType><![CDATA[event]]></MsgType><Event><![CDATA[subscribe]]></Event>
</xml>
  • 扫描带参数二维码

    • 用户未关注时,进行关注后的事件推送
<xml><ToUserName><![CDATA[toUser]]></ToUserName><FromUserName><![CDATA[FromUser]]></FromUserName><CreateTime>123456789</CreateTime><MsgType><![CDATA[event]]></MsgType><Event><![CDATA[subscribe]]></Event><EventKey><![CDATA[qrscene_123123]]></EventKey><Ticket><![CDATA[TICKET]]></Ticket>
</xml>


* 用户已关注时的事件推送

<xml><ToUserName><![CDATA[toUser]]></ToUserName><FromUserName><![CDATA[FromUser]]></FromUserName><CreateTime>123456789</CreateTime><MsgType><![CDATA[event]]></MsgType><Event><![CDATA[SCAN]]></Event><EventKey><![CDATA[SCENE_VALUE]]></EventKey><Ticket><![CDATA[TICKET]]></Ticket>
</xml>


对于 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");// 获取 ticketString ticket = stringMap.get("Ticket");// 将用户扫描的二维码的 Ticket作为 key,值为扫码用户的openId. redisService 代码会在后面贴出。redisService.set(ticket,toUser);// 临时返回信息 xml 字符串             这里有个%s                               这里有个%s                                                    String reply = "<xml><ToUserName><![CDATA[%s]]></ToUserName><FromUserName><![CDATA[%s]]></FromUserName><CreateTime>12345678</CreateTime><MsgType><![CDATA[text]]></MsgType><Content><![CDATA[哈喽!!!]]></Content</xml>";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 = "<xml><ToUserName><![CDATA[%s]]></ToUserName><FromUserName><![CDATA[%s]]></FromUserName><CreateTime>12345678</CreateTime><MsgType><![CDATA[text]]></MsgType><Content><![CDATA[text!!!]]></Content</xml>";String format = String.format(reply, toUser, fromUser);res.getWriter().write(format);}}

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

总结

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

扫码关注公众号登录系统相关推荐

  1. 「实用」微信扫码 - 关注公众号后网站自动登录

    点击上方 好好学java ,选择 星标 公众号 重磅资讯.干货,第一时间送达 今日推荐:用好Java中的枚举,真的没有那么简单!个人原创+1博客:点击前往,查看更多 作者:destiny 链接:htt ...

  2. PHP微信扫码关注公众号并授权登录源码

    PHP微信扫码登录看起来简单,但做起来有点麻烦,开发起来就会浪费很多的时间. PHP判断是否首次关注公众号,扫码关注公众号获取微信用户头像.openid和省市等信息源码. 演示体验地址: https: ...

  3. 最新PHP微信扫码关注公众号并授权登录源码

    正文: PHP微信扫码登录看起来简单,但做起来有点麻烦,开发起来就会浪费很多的时间. PHP判断是否首次关注公众号,扫码关注公众号获取微信用户头像.openid和省市等信息源码. 第一步:获取关注二维 ...

  4. 微信扫码 - 关注公众号后网站自动注册并登录的实现

    微信扫码 - 关注公众号后网站自动注册并登录的实现 需求描述 在自己网站上点击微信登录,网站自己弹出一个二维码.扫描二维码后弹出公众号的关注界面.只要一关注公众号网站自动登录.第二次扫描登录的时候网站 ...

  5. php关注公众号代码,PHP微信扫码关注公众号源码

    附上数据库信息:CREATE TABLE IF NOT EXISTS `qrcode` ( `id` int(11) unsigned NOT NULL, `addtime` int(10) NOT  ...

  6. 生成微信公众号二维码(用户扫码关注公众号)

    1.token 文件 /*** https://mp.weixin.qq.com/advanced/advanced?action=dev&t=advanced/dev&token=1 ...

  7. PHP微信扫码关注公众号并登录

    2019独角兽企业重金招聘Python工程师标准>>> https://www.sucaihuo.com/php/1414.html 转载于:https://my.oschina.n ...

  8. WECHAT 微信扫码关注公众号方法无法获取头像和昵称了

    请注意: 20年6月8日起,用户关注来源"微信广告(ADD_SCENE_WECHAT_ADVERTISEMENT)"从"其他(ADD_SCENE_OTHERS)" ...

  9. 实现微信扫描二维码关注公众号,直接注册登录网站

    互联网时代,不管是以哪种形式存在的应用,移动端或者PC网站,注册登录功能是用户访问应用的第一步,可以说,注册登录用的方不方便在一定程度上能决定用户的去留.对于用户来说,能够越简单,不用动手做过多操作就 ...

最新文章

  1. ## 应用Python爬虫、Flask框架、Echarts、WordCloud等技术实现豆瓣Top250数据分析
  2. 李开复:天才将占领创业领域
  3. 对ThreadLocal实现原理的一点思考
  4. 类似纪念碑谷的unity2d素材包_有哪些免费的音效素材网站?
  5. AI和物联网在零售环境中的长期应用
  6. C#知多少 | 每个版本都更新了什么?
  7. Android之通过HttpURLConnection.getResponseCode状态码抛出异常的问题以及解决方法
  8. power bi示例文件_Power BI桌面问答数据交互示例
  9. Netty 源码深度解析(九) - 编码
  10. vscode 不支持的客户端_Windows平台上有哪些你不知道的神器?
  11. jquery.serialize
  12. mbedtls | 10 - 数字证书及 X.509 证书标准
  13. 11kw星三角启动延时几秒_星三角起动时间继电器应调多少秒
  14. 刘天栋:开源是打破内卷的最好方式
  15. 企业邮箱登录入口首页是哪个,公共电子邮箱怎么申请注册?
  16. ionic知识系列:Could not remove dir ‘/data/data/io.ionic.starter/code_cache/.ll/‘
  17. 看黑科技如何助白娘子逃出雷锋塔!
  18. 2021年所有干货文章导航!#内有惊喜
  19. C语言 while循环和do...while循环
  20. 易语言调用大漠初级入门

热门文章

  1. 2022-2024年最全的计算机软件毕业设计选题大全:1000个热门选题推荐✅
  2. DOM操作词典大全——今日词条:事件基础
  3. oracle如何取当前日期年月_Oracle 中列出当前年所有日期和当前月所有日期
  4. 编程学习方法分享 以及谈谈为什么不建议转行就业的同学自学
  5. 从基于网络的安装服务器安装操作系统,使用Windows2012R2 WDS服务部署Windows 10
  6. 用Python3爬去今日头像图片
  7. postgis 栅格数据_PostGIS入门篇 一 PostGIS安装
  8. 关于二叉查找树的平均深度的O(logN)的数学理论
  9. TI am335x OMAP MUX configure
  10. Python+大数据-知行教育(七)-学生出勤主题看板