从零接入微信公众号(附源码+解说)

前言

  1. 一台云服务器(配置 web 项目环境)
  2. 一个域名(https)

关于服务器方面的,如果你是个小白,可以看下我其它的博客:

CentOS 服务器配置 jdk +Tomcat + mysql

基于腾讯云的域名绑定和 tomcat 部署

腾讯云申请免费 ssl 证书+tomcat配置https

本文是直接上代码,有些需要修改和解释都会说明。

实现接口连接的类

我的 web 开发环境是用 SSM 和 maven 搭建的,不懂可以看我的之前的博客:

eclipse+maven 配置SSM项目环境

SignUtil.java 的实现代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;

/**
* 微信请求校验工具类
*
* @author yy
*
*/
public class SignUtil {
// 与接口配置信息中的 Token 要一致
private static String token = "o2o";

/**
* 验证签名
*
* @param signature
* @param timestamp
* @param nonce
* @return
*/
public static boolean checkSignature(String signature, String timestamp, String nonce) {
String[] arr = new String[] { token, timestamp, nonce };
// 將 token timestamp nonce 三个参数进行字典序排序
Arrays.sort(arr);
StringBuilder content = new StringBuilder();
for (int i = 0; i < arr.length; i++) {
content.append(arr[i]);
}
MessageDigest md = null;
String tmpStr = null;
try {
md = MessageDigest.getInstance("SHA-1");
// 将三个参数字符串拼接成一个字符串进行sha1加密
byte[] digest = md.digest(content.toString().getBytes());
tmpStr = byteToStr(digest);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
content = null;
// 将sha1加密后的字符串可与signature对比,标识该请求来源于微信
return tmpStr != null ? tmpStr.equals(signature.toUpperCase()) : false;
}

/**
* 将字节数组转换为十六进制字符串
*
* @param digest
* @return
*/
private static String byteToStr(byte[] byteArray) {
String strDigest = "";
for (int i = 0; i < byteArray.length; i++) {
strDigest += byteToHexStr(byteArray[i]);
}
return strDigest;
}

/**
* 将字符转换为十六进制字符串
*
* @param b
* @return
*/
private static String byteToHexStr(byte mByte) {
char[] Digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
char[] tempArr = new char[2];
tempArr[0] = Digit[(mByte >>> 4) & 0X0F];
tempArr[1] = Digit[mByte & 0X0F];
String s = new String(tempArr);
return s;
}

}
WechatController.java 的实现代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import com.util.wechat.SignUtil;

@Controller
@RequestMapping("wechat")
public class WechatController {

private static Logger log = LoggerFactory.getLogger(WechatController.class);

@RequestMapping(method = { RequestMethod.GET })
public void doGet(HttpServletRequest request, HttpServletResponse response) {
log.debug("wechat get...");
// 微信加密签名
String signature = request.getParameter("signature");
// 时间戳
String timestamp = request.getParameter("timestamp");
// 随机数
String nonce = request.getParameter("nonce");
// 随机字符串
String echostr = request.getParameter("echostr");

// 通过检验signature对请求进行校验,若校验成功则原样返回echostr,表示接入成功,否则接入失败
PrintWriter out = null;
try {
out = response.getWriter();
if (SignUtil.checkSignature(signature, timestamp, nonce)) {
log.debug("wechat get success....");
out.print(echostr);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (out != null)
out.close();
}
}
}

到这里为止并不需要去修改什么代码,只需要将工程导出为 jar 包扔到自己的云服务器上就行方便后面微信测试号接口的链接测试。记得要放到服务器上,不然访问不到!!!

微信测试号的申请与接口连接

刚刚开始做微信公众号的时候,我们建议选择微信测试号就好,等熟悉了之后,微信公众号的配置什么的,跟测试号是一样的。

接下来就教大家怎么来申请测试号。

首先进入微信公众号的开发文档:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1445241432

Markdown

Markdown

点击“接口配置信息”旁边的“修改”按钮,然后按要求填写内容,然后点击提交,会提示你是否能连接上的。

Markdown

Markdown

Markdown

微信测试号连接代码的实现

用到类的简介

上面测试了微信测试号的接口的可以连接上的,现在来实现手机上的微信测试号是如果连接上的。需要通过五个类来实现,分别是:

  1. WechatLoginController.java
  2. UserAccessToken.java
  3. WechatUser.java
  4. WechatUtil.java
  5. MyX509TrustManager.java

下面会一一讲解这个5个类的实现,以及一些要修改的地方。

提示:我的代码里面使用 debug 输出信息,如果不需要可以把如下面的代码给删除掉。
1
private static Logger log = LoggerFactory.getLogger(WechatLoginController.class);
1
log.debug("weixin login get...");

WechatLoginController.java 的实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
import java.io.IOException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import com.dto.UserAccessToken;
import com.dto.WechatUser;
import com.util.wechat.WechatUtil;

/**
* 获取关注公众号之后的微信用户信息的接口,如果在微信浏览器里访问
* https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxaxxxxxxxxxxxxx&redirect_uri=https://www.yyzheng.cn/o2o/wechatlogin/logincheck&role_type=1&response_type=1&response_type=code&scope=snsapi_userinfo&state=1#wechat_redirect
* 则在这里将会获取到code,之后再通过code获取到access_token进而获取到用户信息
*
* @author yy
*
*/
@Controller
@RequestMapping("wechatlogin")
public class WechatLoginController {
private static Logger log = LoggerFactory.getLogger(WechatLoginController.class);

@RequestMapping(value = "/logincheck", method = { RequestMethod.GET })
public String doGet(HttpServletRequest request, HttpServletResponse response) {
log.debug("weixin login get...");
// 获取微信公众号传输过来的code,通过code可获取access_token,进而获取用户信息
String code = request.getParameter("code");
// 这个State可以用来传我们自定义的信息,方便程序调用,这里也可以不用
// String roleType = request.getParameter("state");
log.debug("weixin login code:" + code);
WechatUser user = null;
String openId = null;
if (null != code) {
UserAccessToken token;
try {
// 通过code获取access_token
token = WechatUtil.getUserAccessToken(code);
log.debug("weixin login token:" + token.toString());
// 通过token获取accessToken
String accessToken = token.getAccessToken();
// 通过token获取openId
openId = token.getOpenId();
// 通过access_token和openId获取用户昵称等信息
user = WechatUtil.getUserInfo(accessToken, openId);
log.debug("weixin login user:" + user.toString());
request.getSession().setAttribute("openId", openId);
} catch (Exception e) {
log.error("error in getUserAccessToken or getUserInfo or findByOpenId: " + e.toString());
e.printStackTrace();
}
}

// ========todo begin ===========
// 前面咱门获取到openid后,可以通过它去数据库判断该微信账号是否在我们网站里有对应的 账号了
// 没有的话这里可以自动创建上,直接实现微信与咱门网站的无缝对接
// ========todo end =============

if (user != null) {
// 获取到微信验证的信息后返回到指定的路由(需要自己设定)
return "frontend/index"; /*******修改******/
} else {
return null;
}
}
}
解析:这段代码需要注意的是最后这一小段代码。

Markdown

这里的意思是,用户进入微信公众号后,验证通过,并判断用户不为空,就跳转到自己想要跳转的页面。所有需要修改掉 return 的值,代码的值是返回到我项目里的其他页面。只要返回到一个简单的页面就行,这里只是做个测试,看微信测试号是否真的能够接入成功,成功就跳转。不成功就不跳转。

UserAccessToken.java 的实现代码

只是一个实体类,用来封装信息的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import com.fasterxml.jackson.annotation.JsonProperty;

/**
* 用户授权token
*
*/
public class UserAccessToken {

//获取到的凭证
@JsonProperty("access_token")
private String accessToken;
//凭证有效时间,单位:秒
@JsonProperty("expires_in")
private String expiresIn;
//表示更新令牌,用来获取下一次的访问令牌,这里没太大用处
@JsonProperty("refresh_token")
private String refreshToken;
//该用户在此公众号下的身份表示,对于此微信号具有唯一性
@JsonProperty("openid")
private String openId;
//表示权限范围,这里可省略
@JsonProperty("scope")
private String scope;

public String getAccessToken() {
return accessToken;
}

public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}

public String getExpiresIn() {
return expiresIn;
}

public void setExpiresIn(String expiresIn) {
this.expiresIn = expiresIn;
}

public String getRefreshToken() {
return refreshToken;
}

public void setRefreshToken(String refreshToken) {
this.refreshToken = refreshToken;
}

public String getOpenId() {
return openId;
}

public void setOpenId(String openId) {
this.openId = openId;
}

public String getScope() {
return scope;
}

public void setScope(String scope) {
this.scope = scope;
}

@Override
public String toString() {
return "accessToken:" + this.getAccessToken() + ",openId:" + this.getOpenId();
}

}

WechatUser.java 的实现代码

这也是一个实体类,用来封装信息的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
import java.io.Serializable;

import com.fasterxml.jackson.annotation.JsonProperty;
/**
* 微信用户实体类
*
*/
public class WechatUser implements Serializable {

private static final long serialVersionUID = -4681067645282292327L;

// openId,标识该公众号下面的该用户的唯一Id
@JsonProperty("openid")
private String openId;
// 用户昵称
@JsonProperty("nick_name")
private String nickName;
// 性别
@JsonProperty("sex")
private int sex;
// 省份
@JsonProperty("province")
private String province;
// 诚实
@JsonProperty("city")
private String city;
// 区
@JsonProperty("country")
private String country;
// 头像图片地址
@JsonProperty("headimgurl")
private String headimgurl;
// 语言
@JsonProperty("language")
private String language;
// 用户权限,这里没什么作用
@JsonProperty("privilege")
private String[] privilege;

public String getOpenId() {
return openId;
}

public void setOpenId(String openId) {
this.openId = openId;
}

public String getNickName() {
return nickName;
}

public void setNickName(String nickName) {
this.nickName = nickName;
}

public int getSex() {
return sex;
}

public void setSex(int sex) {
this.sex = sex;
}

public String getProvince() {
return province;
}

public void setProvince(String province) {
this.province = province;
}

public String getCity() {
return city;
}

public void setCity(String city) {
this.city = city;
}

public String getCountry() {
return country;
}

public void setCountry(String country) {
this.country = country;
}

public String getHeadimgurl() {
return headimgurl;
}

public void setHeadimgurl(String headimgurl) {
this.headimgurl = headimgurl;
}

public String getLanguage() {
return language;
}

public void setLanguage(String language) {
this.language = language;
}

public String[] getPrivilege() {
return privilege;
}

public void setPrivilege(String[] privilege) {
this.privilege = privilege;
}

public static long getSerialversionuid() {
return serialVersionUID;
}

}

WechatUtil.java 的实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.URL;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.controller.wechat.WechatLoginController;
import com.dto.UserAccessToken;
import com.dto.WechatUser;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import net.sf.json.JSONObject;

public class WechatUtil {
private static Logger log = LoggerFactory.getLogger(WechatLoginController.class);

/**
* 获取UserAccessToken实体类
*
* @param code
* @return
*/
public static UserAccessToken getUserAccessToken(String code) {
// 测试号信息里的appId
String appId = "wxaxxxxxxxxxxxxxxxxx";
log.debug("appId:" + appId);
// 测试号信息里的appsecret
String appsecret = "6dxxxxxxxxxxxxxxxxxxxxxxxxxx";
log.debug("appsecret:" + appsecret);
// 根据传入的code,拼接出访问微信定义好的接口的URL
String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + appId + "&secret=" + appsecret
+ "&code=" + code + "&grant_type=authorization_code";
// 向相应URL发送请求获取token json字符串
String tokenStr = httpsRequest(url, "GET", null).toString();
log.debug("userAccessToken:" + tokenStr);
UserAccessToken token = new UserAccessToken();
ObjectMapper objectMapper = new ObjectMapper();
try {
// 将json字符串转换成相应对象
token = objectMapper.readValue(tokenStr, UserAccessToken.class);
} catch (JsonParseException e) {
log.error("获取用户accessToken失败:" + e.getMessage());
e.printStackTrace();
} catch (JsonMappingException e) {
log.error("获取用户accessToken失败:" + e.getMessage());
e.printStackTrace();
} catch (IOException e) {
log.error("获取用户accessToken失败:" + e.getMessage());
e.printStackTrace();
}
if (token == null) {
log.error("获取用户accessToken失败.");
return null;
}
return token;
}

/**
* 发起https请求并获取结果
*
* @param requestUrl
* 请求地址
* @param requestMethod
* 请求方式(GET、post)
* @param outputStr
* 提交的数据
* @return json字符串
*/
private static JSONObject httpsRequest(String requestUrl, String requestMethod, String outputStr) {
StringBuffer buffer = new StringBuffer();
JSONObject jsonObject = null;
try {
// 创建SSLContext对象,并使用我们制定的信任管理器初始化
TrustManager[] tm = { new MyX509TrustManager() };
SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
sslContext.init(null, tm, new java.security.SecureRandom());
// 从上述SSLContext对象中得到SSLSocketFactory对象
SSLSocketFactory ssf = sslContext.getSocketFactory();

URL url = new URL(requestUrl);
HttpsURLConnection httpUrlConn = (HttpsURLConnection) url.openConnection();
httpUrlConn.setSSLSocketFactory(ssf);

httpUrlConn.setDoOutput(true);
httpUrlConn.setDoInput(true);
httpUrlConn.setUseCaches(false);
// 设置请求方式(GET/POST)
httpUrlConn.setRequestMethod(requestMethod);

if ("GET".equalsIgnoreCase(requestMethod)) {
httpUrlConn.connect();
}

// 当有数据需要提交时
if (null != outputStr) {
OutputStream outputStream = httpUrlConn.getOutputStream();
// 注意编码格式,防止中文乱码
outputStream.write(outputStr.getBytes("UTF-8"));
outputStream.close();
}

// 将返回的输入流转换成字符串
InputStream inputStream = httpUrlConn.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

String str = null;
while ((str = bufferedReader.readLine()) != null) {
buffer.append(str);
}
bufferedReader.close();
inputStreamReader.close();
// 释放资源
inputStream.close();
inputStream = null;
httpUrlConn.disconnect();
log.debug("https buffer:" + buffer.toString());
} catch (ConnectException ce) {
log.error("Wechat server connection timed out");
} catch (Exception e) {
log.error("https request error:{}", e);
}
jsonObject = JSONObject.fromObject(buffer.toString());
return jsonObject;
}

public static WechatUser getUserInfo(String accessToken, String openId) {
String url = "https://api.weixin.qq.com/sns/userinfo?access_token=" + accessToken + "&openid=" + openId
+ "&lang=zh_CN";
JSONObject jsonObject = WechatUtil.httpsRequest(url, "GET", null);
WechatUser user = new WechatUser();
String openid = jsonObject.getString("openid");
if (openid == null) {
log.debug("获取用户信息失败。");
return null;
}
user.setOpenId(openid);
user.setNickName(jsonObject.getString("nickname"));
user.setSex(jsonObject.getInt("sex"));
user.setProvince(jsonObject.getString("province"));
user.setCity(jsonObject.getString("city"));
user.setCountry(jsonObject.getString("country"));
user.setHeadimgurl(jsonObject.getString("headimgurl"));
user.setPrivilege(null);
// user.setUnionid(jsonObject.getString("unionid"));
return user;
}
}
解析:
  • 获取 UserAccessToken 工具类,需要修改的是开头的两个地方:

Markdown

  • 我代码里提供的是乱码,所以这里需要把 appid 和 appsecret 修改为自己的,至于在哪里找???你进入自己申请好的微信测试号就行看到了。

  • 代码里面还使用到 JSONObject 这个类,关于这个类的相关 jar 包的引入,之前另一篇博客写过,直接看下就行。博客链接:JSONObject 使用maven引入 net.sf.json 时报错解决方法

MyX509TrustManager.java 的实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.net.ssl.X509TrustManager;

/**
* 证书信任管理器(用于https请求)
*
*/
public class MyX509TrustManager implements X509TrustManager{

@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
// TODO Auto-generated method stub

}

@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
// TODO Auto-generated method stub

}

@Override
public X509Certificate[] getAcceptedIssuers() {
// TODO Auto-generated method stub
return null;
}

}

只是直接继承了 X509TrustManager 这个接口,并实现接口里面的方法,并不需要自己写什么。

到这里,需要实现的 java 类都实现了,同样的道理,需要把整个项目导出 jar 包,然后扔到服务器上。不然访问不到!!!

尝试连接微信测试号

登录微信测试号页面,找到测试号二维码,然后用自己的微信号扫码,使自己的微信账号位于用户列表中。

Markdown

然后,在微信上直接打开下面的链接:

https://open.weixin.qq.com/connect/oauth2/authorize?appid=xxxxxx&redirect_uri=https://www.xxxxxx.cn/o2o/wechatlogin/logincheck&role_type=1&response_type=1&response_type=code&scope=snsapi_userinfo&state=1#wechat_redirect

这个链接,有个参数需要修改下,就是 appid 这个参数后面的值需要换上自己测试号的 appid 哦,还有 redirect_uri 的参数中的域名需要换成自己的域名,还有项目名字如果自己修改了,也要换掉哦。其它的参数不要动。然后把链接发给微信上,用手机微信打开,就能发现成功进入测试号后,会跳转到自己指定的页面。

本文标题:从零接入微信公众号(附源码+解说)

文章作者:小东

发布时间:2019年04月16日 - 00:04

最后更新:2019年05月13日 - 10:05

原始链接:http://zwd596257180.gitee.io/2019/04/16/wechat_account/

许可协议: 转载请保留原文链接及作者。

小东 wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客!