阿里云部署Java网站和微信开发调试心得技巧(下)
五、微信测试号的申请与连接以获取微信用户信息
在咱们自己的程序里面编写servlet以响应微信号
在接下来的步骤中,我们将在测试号里面设置接口配置信息的URL,一经设置,微信公众号便会发请求到我们设置好的URL去,我们必须编写程序应答才能顺利连通微信公众号,因此咱们需要编写相应的响应程序
需要编写两个类
【SignUtil】
package com.imooc.o2o.util.weixin;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
/**
* 微信请求校验工具类
*/
public class SignUtil {
// 与接口配置信息中的Token要一致
private static String token = "myo2o";
/**
* 验证签名
*
* @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 byteArray
* @return
*/
private static String byteToStr(byte[] byteArray) {
String strDigest = "";
for (int i = 0; i < byteArray.length; i++) {
strDigest += byteToHexStr(byteArray[i]);
}
return strDigest;
}
/**
* 将字节转换为十六进制字符串
*
* @param mByte
* @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】
package com.imooc.o2o.web.wechat;
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.imooc.o2o.util.wechat.SignUtil;
@Controller
//一会在设置的URL里面就设置上这个路由
@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("weixin get...");
// 微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
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("weixin get success....");
out.print(echostr);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (out != null)
out.close();
}
}
}
之后重新部署一版最新的程序
访问微信测试号登录页面,通过打开自己手机的微信,扫一扫登录
https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login
进入到测试号页面后,分别看到如下信息
【测试号信息】
appID:开发者ID,是公众号开发识别码,配合开发者密码可以调用微信公众号接口,如获取微信昵称等
appsecret:开发者密码,是检验公众号开发者身份的密码,具有极高的安全性。切记不要把密码交给第三方开发者或者编写到代码里
【接口配置信息】
URL: 是开发者用来接收微信消息和事件的接口URL
Token:由开发者可以任意填写,用作生成签名(该Token会和接口URL中包含的Token进行比对,从而验证安全性)
【JS接口安全域名】
域名:想调用jssdk(如想要通过微信公众号js接口获取地图等工具)必须得填写此域名,在此域名的范围内才能调用jssdk工具,注意这里必须是域名,不是带有http之类的URL,这里直接填写o2o.yitiaojieinfo.com
【测试号二维码】
里面包含了测试号二维码以及已经关注了的用户信息
【体验接口权限表】
这里主要介绍【网页服务】里面的【网页帐号】
网页帐号主要用来设置OAuth2.0里面的网页授权域名,用户在网页授权页同意授权给公众号后,微信会将授权数据传给一个回调页面,回调页面需在此域名下,以确保安全可靠。沙盒号回调地址支持域名和ip,正式公众号回调地址只支持域名。这里直接设置为o2o.yitiaojieinfo.com
有不清楚的地方可以直接参考微信官方文档
https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421135319
接下来需要编写自己的程序以获取关注此公众号的用户信息
需要编写5个类 WechatLoginController.java,UserAccessToken.java,WechatUser.java,WechatUtil.java以及MyX509TrustManager.java
【WechatLoginController】主要用来获取已关注此微信号的用户信息并做相应处理
package com.imooc.o2o.web.wechat;
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.imooc.o2o.dto.UserAccessToken;
import com.imooc.o2o.dto.WechatUser;
import com.imooc.o2o.util.wechat.WeiXinUserUtil;
@Controller
@RequestMapping("wechatlogin")
/**
* 获取关注公众号之后的微信用户信息的接口,如果在微信浏览器里访问
* https://open.weixin.qq.com/connect/oauth2/authorize?appid=您的appId&redirect_uri=http://o2o.yitiaojieinfo.com/o2o/wechatlogin/logincheck&role_type=1&response_type=code&scope=snsapi_userinfo&state=1#wechat_redirect
* 则这里将会获取到code,之后再可以通过code获取到access_token 进而获取到用户信息
*
* @author xiangze
*
*/
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 = WeiXinUserUtil.getUserAccessToken(code);
log.debug("weixin login token:" + token.toString());
// 通过token获取accessToken
String accessToken = token.getAccessToken();
// 通过token获取openId
openId = token.getOpenId();
// 通过access_token和openId获取用户昵称等信息
user = WeiXinUserUtil.getUserInfo(accessToken, openId);
log.debug("weixin login user:" + user.toString());
request.getSession().setAttribute("openId", openId);
} catch (IOException 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;
}
}
}
【UserAccessToken】用户AccessToken实体类,用来接收accesstoken以及openid等信息
package com.imooc.o2o.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* 用户授权token
*
* @author xiangze
*
*/
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】微信用户实体类,用来接收昵称 openid等用户信息
package com.imooc.o2o.dto;
import java.io.Serializable;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* 微信用户实体类
*
* @author xiangze
*
*/
public class WechatUser implements Serializable {
/**
*
*/
private static final long serialVersionUID = -4684067645282292327L;
// openId,标识该公众号下面的该用户的唯一Id
@JsonProperty("openid")
private String openId;
// 用户昵称
@JsonProperty("nickname")
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;
}
@Override
public String toString() {
return "openId:" + this.getOpenId() + ",nikename:" + this.getNickName();
}
}
【WechatUtil】主要用来提交https请求给微信获取用户信息
package com.imooc.o2o.util.wechat;
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.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.imooc.o2o.dto.UserAccessToken;
import com.imooc.o2o.dto.WechatUser;
/**
* 微信工具类
*
* @author xiangze
*
*/
public class WechatUtil {
private static Logger log = LoggerFactory.getLogger(WechatUtil.class);
/**
* 获取UserAccessToken实体类
*
* @param code
* @return
* @throws IOException
*/
public static UserAccessToken getUserAccessToken(String code) throws IOException {
// 测试号信息里的appId
String appId = "您的appId";
log.debug("appId:" + appId);
// 测试号信息里的appsecret
String appsecret = "您的appsecret";
log.debug("secret:" + 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);
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;
}
/**
* 获取WechatUser实体类
*
* @param accessToken
* @param openId
* @return
*/
public static WechatUser getUserInfo(String accessToken, String openId) {
// 根据传入的accessToken以及openId拼接出访问微信定义的端口并获取用户信息的URL
String url = "https://api.weixin.qq.com/sns/userinfo?access_token=" + accessToken + "&openid=" + openId
+ "&lang=zh_CN";
// 访问该URL获取用户信息json 字符串
String userStr = httpsRequest(url, "GET", null);
log.debug("user info :" + userStr);
WechatUser user = new WechatUser();
ObjectMapper objectMapper = new ObjectMapper();
try {
// 将json字符串转换成相应对象
user = objectMapper.readValue(userStr, WechatUser.class);
} catch (JsonParseException e) {
log.error("获取用户信息失败: " + e.getMessage());
e.printStackTrace();
} catch (JsonMappingException e) {
log.error("获取用户信息失败: " + e.getMessage());
e.printStackTrace();
} catch (IOException e) {
log.error("获取用户信息失败: " + e.getMessage());
e.printStackTrace();
}
if (user == null) {
log.error("获取用户信息失败。");
return null;
}
return user;
}
/**
* 发起https请求并获取结果
*
* @param requestUrl
* 请求地址
* @param requestMethod
* 请求方式(GET、POST)
* @param outputStr
* 提交的数据
* @return json字符串
*/
public static String httpsRequest(String requestUrl, String requestMethod, String outputStr) {
StringBuffer buffer = new StringBuffer();
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("Weixin server connection timed out.");
} catch (Exception e) {
log.error("https request error:{}", e);
}
return buffer.toString();
}
}
【MyX509TrustManager】主要继承X509TrustManager做https证书信任管理器
package com.imooc.o2o.util.wechat;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.X509TrustManager;
/**
* 证书信任管理器(用于https请求)
*
* @author xiangze
*
*/
public class MyX509TrustManager implements X509TrustManager {
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
}
之后重新打包一个新的war包并发布到服务器tomcat webapps目录下
发布成功后,关注你自己的测试号(即扫描测试号的那个二维码),然后在手机微信里面或者微信开发者工具里访问相应链接:
https://open.weixin.qq.com/connect/oauth2/authorize?appid=您的appid&redirect_uri=http://o2o.yitiaojieinfo.com/o2o/wechatlogin/logincheck&role_type=1&response_type=code&scope=snsapi_userinfo&state=1#wechat_redirect
之后查看日志信息,便能发现确实能够获取到用户的信息了
至此咱们已经从无到有完成了阿里云部署Java网站和微信公众号的开发,文章虽长,但是干货多多,都是傻瓜式的配置,希望大家喜欢。
注意事项:
- 由于tomcat咱们没有调优,启动较慢,建议每次重启等待几分钟再去访问,否则会假死,需要kill掉再重启
- 经过认证的微信服务号跟测试号其实是一样的,大家可以按照上述去配置
热门评论
老师,我在本地跑项目,图片无法显示,能否将项目使用的缩略图upload,文件夹发我一份,万分感激!
老师,我在本地跑项目,图片无法显示,能否将项目使用的缩略图upload,文件夹发我一份,万分感激!
上一篇手记删掉了吗???