一、新建项目工程

  1. 新建一个spring项目

  2. 填写 Group 和 Artifact 信息

  3. 这步可以直接跳过,后面再按需导入

  4. 选择工程地址

二、配置

pom.xml

<dependencies>
<!-- spring相关包 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- http请求 -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.6</version>
</dependency>
<!-- 工具包 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.5.8</version>
</dependency>
<!-- xml解析工具包 -->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
</dependencies>

application.yml

spring:
application:
name: wechat server:
port: 8900 appid: # 微信开发者appid
secret: # 微信开发者appsecret
token: # 服务器校验token

三、相关类

AccessToken / AccessTokenInfo

/**
* AccessToken 实体类
* @author unidentifiable
* @date 2021-02-28
*/
public class AccessToken {
/**
* 获取到的凭证
*/
private String tokenName;
/**
* 凭证有效时间 单位:秒
*/
private int expireSecond; /**
* get/set ...
*/
} /**
* AccessToken 实体类
* @author unidentifiable
* @date 2021-02-28
*/
public class AccessTokenInfo {
/**
* accessToken:像微信端发起请求需携带该accessToken
*/
public static AccessToken accessToken = null;
}

MessageUtil

/**
* 信息工具类
* @author unidentifiable
* @date 2021-02-28
*/
public class MessageUtil {
/**
* 解析微信发来的请求(XML)
*
* @param request 请求
* @return map
* @throws Exception 异常
*/
public static Map<String, String> parseXml(HttpServletRequest request) {
// 将解析结果存储在HashMap中
Map<String, String> map = new HashMap<>(16);
// 从request中取得输入流
try (InputStream inputStream = request.getInputStream()) {
System.out.println("获取输入流");
// 读取输入流
SAXReader reader = new SAXReader();
Document document = reader.read(inputStream);
// 得到xml根元素
Element root = document.getRootElement();
// 得到根元素的所有子节点
List<Element> elementList = root.elements(); // 遍历所有子节点
for (Element e : elementList) {
System.out.println(e.getName() + " | " + e.getText());
map.put(e.getName(), e.getText());
}
} catch (Exception e) {
e.printStackTrace();
} return map;
} /**
* 获取用户详情
* @param openId 微信公众号用户ID
* @return 用户详情
*/
public static String getUserInfo(String openId) {
// 拼接 url,发起 http.get 请求
String url = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=" + AccessTokenInfo.accessToken.getTokenName() + "&openid=" + openId + "&lang=zh_CN";
return HttpUtil.get(url);
}
}

AccessTokenConfig

/**
* AccessToken 配置类
*
* @author unidentifiable
* @date 2021-02-28
*/
public class AccessTokenConfig { static {
Properties prop = new Properties();
String appid = null;
String secret = null;
try {
// 读取 application.yml 获取微信开发者 appid 和 appsecret
InputStream in = MessageUtil.class.getClassLoader().getResourceAsStream("application.yml");
prop.load(in);
appid = prop.getProperty("appid");
secret = prop.getProperty("secret");
} catch (Exception e) {
e.printStackTrace();
} // 接口地址为https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET,其中grant_type固定写为client_credential即可。
String url = String.format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s", appid, secret);
// 此请求为https的get请求,返回的数据格式为{"access_token":"ACCESS_TOKEN","expires_in":7200}
String result = HttpUtil.get(url); System.out.println("获取到的access_token=" + result); //使用FastJson将Json字符串解析成Json对象
JSONObject json = new JSONObject(result);
AccessToken token = new AccessToken();
token.setTokenName(json.get("access_token", String.class));
token.setExpireSecond(json.get("expires_in", Integer.class)); // 开启一个线程,循环更新 AccessToken,该值有效期 2小时,需手动刷新
new Thread(() -> {
while (true) {
try {
// 获取accessToken
AccessTokenInfo.accessToken = token;
// 获取成功
if (AccessTokenInfo.accessToken != null) {
// 获取到access_token 休眠7000秒,大约2个小时左右
Thread.sleep(7000 * 1000);
} else {
// 获取的access_token为空 休眠3秒
Thread.sleep(3000);
}
} catch (Exception e) {
System.out.println("发生异常:" + e.getMessage());
e.printStackTrace();
try {
// 发生异常休眠1秒
Thread.sleep(1000);
} catch (Exception e1) {
e.printStackTrace();
}
}
}
}).start();
}
}

SubscribeService

package com.unidentifiable.wechat.service;

import com.unidentifiable.wechat.util.MessageUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import javax.servlet.http.HttpServletRequest;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Map; /**
* Service
* @author unidentifiable
* @date 2021-02-28
*/
@Service("subscribeService")
public class SubscribeService {
@Value("${subscriptionKey}")
public String subscriptionKey;
@Value("${uriBase}")
public String uriBase;
@Value("${token}")
public String token; /**
* 公众号服务器校验
*
* @param req 请求
* @return 校验结果
*/
public String verification(HttpServletRequest req) {
// 接收微信服务器发送请求时传递过来的参数
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 = sort(token, timestamp, nonce);
// 字符串进行shal加密
String mySignature = shal(sortStr);
// 校验微信服务器传递过来的签名 和 加密后的字符串是否一致, 若一致则签名通过
if (!"".equals(signature) && !"".equals(mySignature) && signature.equals(mySignature)) {
System.out.println("-----签名校验通过-----");
return echostr;
} else {
System.out.println("-----校验签名失败-----");
return "";
}
} /**
* 参数排序
*
* @param token 自定义token
* @param timestamp timestamp
* @param nonce nonce
* @return 排序后拼接字符串
*/
public String sort(String token, String timestamp, String nonce) {
String[] strArray = {token, timestamp, nonce};
Arrays.sort(strArray);
StringBuilder sb = new StringBuilder();
for (String str : strArray) {
sb.append(str);
}
return sb.toString();
} /**
* 字符串进行shal加密
*
* @param str 字符串
* @return 密文
*/
public String shal(String str) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-1");
digest.update(str.getBytes());
byte messageDigest[] = digest.digest(); StringBuilder hexString = new StringBuilder();
// 字节数组转换为 十六进制 数
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 "";
} /**
* 关注请求
*
* @param req 请求
* @return 关注事件结果
*/
public String subscribeEvent(HttpServletRequest req) {
String result = null;
Map<String, String> map = null; try {
/*
解析请求,得到相关信息
FromUserName| openid 用户ID
CreateTime|1614499703 时间
MsgType|event 消息类型
Event|subscribe 事件类型
EventKey|
*/
map = MessageUtil.parseXml(req);
} catch (Exception e) {
e.printStackTrace();
} // 新开一个线程防止当前现在延时导致微信重复发起请求
final Map<String, String> m = map;
final String[] str = new String[1];
new Thread(() -> {
// 获取判断是否为关注事件
if (null != m && "event".equals(m.get("MsgType")) && "subscribe".equals(m.get("Event"))) {
str[0] = MessageUtil.getUserInfo(m.get("FromUserName"));
System.out.println(str[0]); // 这里可以添加邮件通知等功能,也可以判断别的事件,做出对应操作
}
}).start(); return result;
} }

SubscribeController

package com.unidentifiable.wechat.controller;

import com.unidentifiable.wechat.service.SubscribeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping; import javax.servlet.http.HttpServletRequest; /**
* Controller
* @author unidentifiable
* @date 2021-02-28
*/
public class SubscribeController {
@Autowired
private SubscribeService subscribeService; /**
* 服务器校验
* @param req 请求
* @return 校验结果
*/
@GetMapping("/")
public String verification(HttpServletRequest req) {
return subscribeService.verification(req);
} /**
* 消息事件
* @param req 请求
* @return 结果
*/
@PostMapping("/")
public boolean subscribeEvent(HttpServletRequest req) {
String s = subscribeService.subscribeEvent(req);
return null != s;
}
}

四、内网穿透(有域名的这一步可以省略)

这里使用的是 natapp 这一款工具,下载好对应版本工具

https://natapp.cn/

新建一个免费的隧道,配置好需要映射的地址跟端口,然后复制 authtoken



修改客户端配置文件中的 authtoken(我这里是win版本,linux版本没有该文件,建议新增一份,不然后台启动可能会连接超时)

config.ini

#将本文件放置于natapp同级目录 程序将读取 [default] 段
#在命令行参数模式如 natapp -authtoken=xxx 等相同参数将会覆盖掉此配置
#命令行参数 -config= 可以指定任意config.ini文件
[default]
authtoken= #对应一条隧道的authtoken
clienttoken= #对应客户端的clienttoken,将会忽略authtoken,若无请留空,
log=none #log 日志文件,可指定本地文件, none=不做记录,stdout=直接屏幕输出 ,默认为none
loglevel=DEBUG #日志等级 DEBUG, INFO, WARNING, ERROR 默认为 DEBUG
http_proxy= #代理设置 如 http://10.123.10.10:3128 非代理上网用户请务必留空

配置完成直接双击运行即可,linux版后台启动可用 (./natapp &) 命令运行,注意是有括号的。出现如下界面即为成功。

五、微信公众平台接口测试账号

https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login

拿到 appID 跟 appsecret 填入 appliation.yml 配置文件中,启动项目,启动 natapp ,填入域名以及自定义的token 进行接口配置校验。

绑定完成服务器信息之后,就可以扫描下面的二维码进行测试了。

教程到这里就结束了,大家有想实现什么自定义功能的话可以查看微信官方文档,看里面有提供什么功能,然后自己再修改下对应的判断即可。

项目地址: https://gitee.com/unidentifiable/wechatpublicaccount

Java获取微信公众号新增用户事件的更多相关文章

  1. ASP.NET Core2实现静默获取微信公众号的用户OpenId

    最近在做个微信公众号的项目,需要将入口放置在公众号二级菜单内,通过点击该菜单链接后进入到该项目中去,进入到项目后程序会自动通过微信公众号的API完成用户的OpenId获取.需求很简单,实现起来也不复杂 ...

  2. java 获取微信公众号code为空

    失败的原因是没将回调方法encode转换 /** * URL编码(utf-8) * * @param source * @return */ public static String urlEncod ...

  3. Java开发微信公众号(五)---微信开发中如何获取access_token以及缓存access_token

    获取access_token是微信api最重要的一个部分,因为调用其他api很多都需要用到access_token.比如自定义菜单接口.客服接口.获取用户信息接口.用户分组接口.群发接口等在请求的时候 ...

  4. Java开发微信公众号(三)---微信服务器请求消息,响应消息,事件消息以及工具处理类的封装

    在前面几篇文章我们讲了微信公众号环境的配置 和微信公众号服务的接入,接下来我们来说一下微信服务器请求消息,响应消息以及事件消息的相关内容,首先我们来分析一下消息类型和返回xml格式及实体类的封装. ( ...

  5. Java开发微信公众号(四)---微信服务器post消息体的接收及消息的处理

    在前几节文章中我们讲述了微信公众号环境的搭建.如何接入微信公众平台.以及微信服务器请求消息,响应消息,事件消息以及工具处理类的封装:接下来我们重点说一下-微信服务器post消息体的接收及消息的处理,这 ...

  6. 微信小程序与微信公众号同一用户登录问题

    微信小程序与微信公众号同一用户登录问题 最近在做微信小程序与微信公众号登录合并的接口.整理相关资料以及个人认识的心得写了这篇文章与大家一起分享. 首先,简单说下我遇到的问题是我们的程序调用微信小程序得 ...

  7. Java开发微信公众号(二)---开启开发者模式,接入微信公众平台开发

    接入微信公众平台开发,开发者需要按照如下步骤完成: 1.填写服务器配置 2.验证服务器地址的有效性 3.依据接口文档实现业务逻辑 资料准备: 1.一个可以访问的外网,即80的访问端口,因为微信公众号接 ...

  8. Java开发微信公众号(一)---初识微信公众号以及环境搭建

    ps:1.开发语言使用Java springMvc+Mybaits+spring maven实现 2.使用微信接口测试账号进行本地测试 https://mp.weixin.qq.com/debug/c ...

  9. 带领技术小白入门——基于java的微信公众号开发(包括服务器配置、java web项目搭建、tomcat手动发布web项目、微信开发所需的url和token验证)

    微信公众号对于每个人来说都不陌生,但是许多人都不清楚是怎么开发的.身为技术小白的我,在闲暇之余研究了一下基于java的微信公众号开发.下面就是我的实现步骤,写的略显粗糙,希望大家多多提议! 一.申请服 ...

随机推荐

  1. VScode 连接虚拟机

    VScode 连接虚拟机 在VScode上面使用SSH连接虚拟机,编写代码以及运行都将会方便许多 打开VScode,安装Remote-SSH插件 配置SSH连接信息 点击左侧第四个图标,然后单击设置按 ...

  2. GPLT L2-010 排座位 (并查集)

    Tips: 数据范围较小时可把二维数组当做map<pair<int,int>,int>使用. #include <bits/stdc++.h> using name ...

  3. codeforces 86D D. Powerful array

    An array of positive integers a1, a2, ..., an is given. Let us consider its arbitrary subarray al, a ...

  4. Codeforces Round #670 (Div. 2) C. Link Cut Centroids (dfs,树)

    C. Link Cut Centroids Fishing Prince loves trees, and he especially loves trees with only one centro ...

  5. 被收费绘图工具 PUA 了怎么办?来看看这个老实工具吧

    本文非常适合 Electron 入门选手,墙裂推荐! 本文作者:HelloGitHub-蔡文心 大家好!这里是 HelloGitHub 推出的<讲解开源项目>系列,今天给大家带来的一款基于 ...

  6. K8s Deployment YAML 名词解释

    Deployment 简述 Deployment 为 Pod 和 ReplicaSet 提供了一个声明式定义 (declarative) 方法,用来替代以前的 ReplicationControlle ...

  7. Kubernets二进制安装(7)之部署主控节点服务--apiserver二进制安装

    kube-apiserver集群规划 主机名 角色 IP地址 mfyxw30.mfxyw.com kube-apiserver主 192.168.80.30 mfyxw40.mfyxw.com kub ...

  8. Seq2Seq原理详解

    一.Seq2Seq简介 seq2seq 是一个Encoder–Decoder 结构的网络,它的输入是一个序列,输出也是一个序列.Encoder 中将一个可变长度的信号序列变为固定长度的向量表达,Dec ...

  9. javascript和XML

    一,浏览器对XML DOM的支持1,DOM2级核心 var xmldom = document.implementation.createDocument("","roo ...

  10. Java之大数相加

    之前参加某公司笔试,机试题目是大数相加,两大数是字符串形式,求和. 当时讨巧用的是BigDecimal类,但是发迷糊了,以为b1.add(b2)后,和就加到b1上了,结果一直输出不对. 其实应该是这样 ...