提前准备:
1.一台云服务器(配置好web项目环境)
2.一个域名(https)
关于服务器的,如果你是小白可以看我的其他博客:
CentOS 服务器配置 jdk +Tomcat + mysql
本文是直接上代码,有些需要修改和解释都会说明。
一、实现接口连接的类( SignUtil 和 WechatController )
我的 web 开发环境是用 SSM 和 maven 搭建的,不懂可以看我的另外一篇博客:
SignUtil.java 的实现代码
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 的实现代码
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
点击“接口配置信息”旁边的“修改”按钮,然后按要求填写内容,然后点击提交,会提示你是否能连接上的。
三、微信测试号连接代码的实现
上面测试了微信测试号的接口的可以连接上的,现在来实现手机上的微信测试号是如果连接上的。需要通过五个类来实现,分别是:
WechatLoginController.java
UserAccessToken.java
WechatUser.java
WechatUtil.java
MyX509TrustManager.java
下面会一一讲解这个5个类的实现,以及一些要修改的地方。
提示:
我的代码里面使用 debug 输出信息,如果不需要可以把如下面的代码给删除掉:
private static Logger log = LoggerFactory.getLogger(WechatLoginController.class);
log.debug("weixin login get...");
WechatLoginController.java 的实现代码:
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;
}
}
}
解析:
这段代码需要注意的是最后这一小段代码:
这里的意思是,用户进入微信公众号后,验证通过,并判断用户不为空,就跳转到自己想要跳转的页面。所有需要修改掉 return 的值,代码的值是返回到我项目里的其他页面。只要返回到一个简单的页面就行,这里只是做个测试,看微信测试号是否真的能够接入成功,成功就跳转。不成功就不跳转。
UserAccessToken.java 的实现代码
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 的实现代码
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 的实现代码
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;
}
}
解析
1.获取UserAccessToken工具类,需要修改的是开头的两个地方:
我代码里提供的是乱码,所以这里需要把 appid 和 appsecret 修改为自己的,至于在哪里找???你进入自己申请好的微信测试号就行看到了。
2.代码里面还使用到 JSONObject 这个类,关于这个类的相关 jar 包的引入,之前另一篇博客写过,直接看下就行:
JSONObject 使用maven引入 net.sf.json 时报错解决方法
MyX509TrustManager.java 的实现代码
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 包,然后扔到服务器上。不然访问不到!!!
四、尝试连接微信测试号
登录微信测试号页面,找到测试号二维码,然后用自己的微信号扫码,使自己的微信账号位于用户列表中。
然后,在微信上直接打开下面的链接:
这个链接,有个参数需要修改下,就是 appid 这个参数后面的值需要换上自己测试号的 appid 哦,还有 redirect_uri 的参数中的域名需要换成自己的域名,还有项目名字如果自己修改了,也要换掉哦。其它的参数不要动。然后把链接发给微信上,用手机微信打开,就能发现成功进入测试号后,会跳转到自己指定的页面。