1、整体流程
来源于官方文档
2、添加配置
appid appsecret 需要去微信开发平台注册 https://open.weixin.qq.com/
我这里使用谷粒学院项目的
# 微信开放平台 appid
wx.open.app_id=wxed9954c01bb89b47
# 微信开放平台 appsecret
wx.open.app_secret=a7482517235173ddb4083788de60b90e
# 微信开放平台 重定向url你的网站
wx.open.redirect_url=http://guli.shop/api/ucenter/wx/callback
3、创建常量类
@Componentpublic
class ConstantWxUtils implements InitializingBean {
@Value("${wx.open.app_id}")
private String appId;
@Value("${wx.open.app_secret}")
private String appSecret;
@Value("${wx.open.redirect_url}")
private String RedirectUrl;
public static String APP_ID;
public static String APP_SECRET;
public static String REDIRECT_URL;
@Override
public void afterPropertiesSet() throws Exception {
APP_ID = appId;
APP_SECRET = appSecret;
REDIRECT_URL = RedirectUrl;
}
}
4、生成微信二维码
流程位置
Service层
/**
*生成微信二维码
*/
public String getWxCode() {
/*
固定网址,参数作用
appid
应用唯一标识
redirect_uri
请使用urlEncode对链接进行处理
response_type
填code
scope
应用授权作用域,拥有多个作用域用逗号(,)分隔,网页应用目前仅填写snsapi_login
state
用于保持请求和回调的状态,授权请求后原样带回给第三方。该参数可用于防止csrf攻击(跨站请求伪造攻击),建议第三方带上该参数,可设置为简单的随机数加session进行校验
*/
String url = "https://open.weixin.qq.com/connect/qrconnect?" +
"appid=%s" +
"&redirect_uri=%s" +
"&response_type=code" +
"&scope=snsapi_login" +
"&state=%s" +
"#wechat_redirect";
String encodeRedirectUrl = ConstantWxUtils.REDIRECT_URL;
try {
encodeRedirectUrl = URLEncoder.encode(encodeRedirectUrl, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
url = String.format(
url,
ConstantWxUtils.APP_ID,
encodeRedirectUrl,
"Tcl_guli"
);
return url;
}
Controller层
@ApiOperation("生成微信二维码")
@GetMapping()
public String getWxCode() {
String url = wxApiService.getWxCode();
return "redirect:" + url;
}
调用后成功会出现微信二维码
5、扫码登录获得用户信息
5.1 导入依赖
扫码登录获得用户信息过程中会向固定网址发送请求所以要使用httpclient发送请求(向浏览器发送请求一样,只不过这次是在代码中)
<!--httpclient 发送请求-->
<dependency>
<groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId>
</dependency>
<!--commons-io 发送请求工具类需要的依赖-->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>
<!--gson json转换工具-->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
5.2 Httpclient工具类
直接用,不用看
package com.liang.edu.utils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.config.RequestConfig.Builder;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.conn.ssl.X509HostnameVerifier;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.security.GeneralSecurityException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/**
* 依赖的jar包有:commons-lang-2.6.jar、httpclient-4.3.2.jar、httpcore-4.3.1.jar、commons-io-2.4.jar
*
* @author zhaoyb
*/
public class HttpClientUtils {
public static final int connTimeout = 10000;
public static final int readTimeout = 10000;
public static final String charset = "UTF-8";
private static HttpClient client = null;
static {
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
cm.setMaxTotal(128);
cm.setDefaultMaxPerRoute(128);
client = HttpClients.custom().setConnectionManager(cm).build();
}
public static String postParameters(String url, String parameterStr) throws ConnectTimeoutException, SocketTimeoutException, Exception {
return post(url, parameterStr, "application/x-www-form-urlencoded", charset, connTimeout, readTimeout);
}
public static String postParameters(String url, String parameterStr, String charset, Integer connTimeout, Integer readTimeout) throws ConnectTimeoutException, SocketTimeoutException, Exception {
return post(url, parameterStr, "application/x-www-form-urlencoded", charset, connTimeout, readTimeout);
}
public static String postParameters(String url, Map<String, String> params) throws ConnectTimeoutException,
SocketTimeoutException, Exception {
return postForm(url, params, null, connTimeout, readTimeout);
}
public static String postParameters(String url, Map<String, String> params, Integer connTimeout, Integer readTimeout) throws ConnectTimeoutException,
SocketTimeoutException, Exception {
return postForm(url, params, null, connTimeout, readTimeout);
}
public static String get(String url) throws Exception {
return get(url, charset, null, null);
}
public static String get(String url, String charset) throws Exception {
return get(url, charset, connTimeout, readTimeout);
}
/**
* 发送一个 Post 请求, 使用指定的字符集编码.
*
* @param url
* @param body RequestBody
* @param mimeType 例如 application/xml "application/x-www-form-urlencoded" a=1&b=2&c=3
* @param charset 编码
* @param connTimeout 建立链接超时时间,毫秒.
* @param readTimeout 响应超时时间,毫秒.
* @return ResponseBody, 使用指定的字符集编码.
* @throws ConnectTimeoutException 建立链接超时异常
* @throws SocketTimeoutException 响应超时
* @throws Exception
*/
public static String post(String url, String body, String mimeType, String charset, Integer connTimeout, Integer readTimeout)
throws ConnectTimeoutException, SocketTimeoutException, Exception {
HttpClient client = null;
HttpPost post = new HttpPost(url);
String result = "";
try {
if (StringUtils.isNotBlank(body)) {
HttpEntity entity = new StringEntity(body, ContentType.create(mimeType, charset));
post.setEntity(entity);
}
// 设置参数
Builder customReqConf = RequestConfig.custom();
if (connTimeout != null) {
customReqConf.setConnectTimeout(connTimeout);
}
if (readTimeout != null) {
customReqConf.setSocketTimeout(readTimeout);
}
post.setConfig(customReqConf.build());
HttpResponse res;
if (url.startsWith("https")) {
// 执行 Https 请求.
client = createSSLInsecureClient();
res = client.execute(post);
} else {
// 执行 Http 请求.
client = com.liang.edu.utils.HttpClientUtils.client;
res = client.execute(post);
}
result = IOUtils.toString(res.getEntity().getContent(), charset);
} finally {
post.releaseConnection();
if (url.startsWith("https") && client != null && client instanceof CloseableHttpClient) {
((CloseableHttpClient) client).close();
}
}
return result;
}
/**
* 提交form表单
*
* @param url
* @param params
* @param connTimeout
* @param readTimeout
* @return
* @throws ConnectTimeoutException
* @throws SocketTimeoutException
* @throws Exception
*/
public static String postForm(String url, Map<String, String> params, Map<String, String> headers, Integer connTimeout, Integer readTimeout) throws ConnectTimeoutException,
SocketTimeoutException, Exception {
HttpClient client = null;
HttpPost post = new HttpPost(url);
try {
if (params != null && !params.isEmpty()) {
List<NameValuePair> formParams = new ArrayList<NameValuePair>();
Set<Entry<String, String>> entrySet = params.entrySet();
for (Entry<String, String> entry : entrySet) {
formParams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
}
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formParams, Consts.UTF_8);
post.setEntity(entity);
}
if (headers != null && !headers.isEmpty()) {
for (Entry<String, String> entry : headers.entrySet()) {
post.addHeader(entry.getKey(), entry.getValue());
}
}
// 设置参数
Builder customReqConf = RequestConfig.custom();
if (connTimeout != null) {
customReqConf.setConnectTimeout(connTimeout);
}
if (readTimeout != null) {
customReqConf.setSocketTimeout(readTimeout);
}
post.setConfig(customReqConf.build());
HttpResponse res = null;
if (url.startsWith("https")) {
// 执行 Https 请求.
client = createSSLInsecureClient();
res = client.execute(post);
} else {
// 执行 Http 请求.
client = com.liang.edu.utils.HttpClientUtils.client;
res = client.execute(post);
}
return IOUtils.toString(res.getEntity().getContent(), "UTF-8");
} finally {
post.releaseConnection();
if (url.startsWith("https") && client != null
&& client instanceof CloseableHttpClient) {
((CloseableHttpClient) client).close();
}
}
}
/**
* 发送一个 GET 请求
*
* @param url
* @param charset
* @param connTimeout 建立链接超时时间,毫秒.
* @param readTimeout 响应超时时间,毫秒.
* @return
* @throws ConnectTimeoutException 建立链接超时
* @throws SocketTimeoutException 响应超时
* @throws Exception
*/
public static String get(String url, String charset, Integer connTimeout, Integer readTimeout)
throws ConnectTimeoutException, SocketTimeoutException, Exception {
HttpClient client = null;
HttpGet get = new HttpGet(url);
String result = "";
try {
// 设置参数
Builder customReqConf = RequestConfig.custom();
if (connTimeout != null) {
customReqConf.setConnectTimeout(connTimeout);
}
if (readTimeout != null) {
customReqConf.setSocketTimeout(readTimeout);
}
get.setConfig(customReqConf.build());
HttpResponse res = null;
if (url.startsWith("https")) {
// 执行 Https 请求.
client = createSSLInsecureClient();
res = client.execute(get);
} else {
// 执行 Http 请求.
client = com.liang.edu.utils.HttpClientUtils.client;
res = client.execute(get);
}
result = IOUtils.toString(res.getEntity().getContent(), charset);
} finally {
get.releaseConnection();
if (url.startsWith("https") && client != null && client instanceof CloseableHttpClient) {
((CloseableHttpClient) client).close();
}
}
return result;
}
/**
* 从 response 里获取 charset
*
* @param ressponse
* @return
*/
@SuppressWarnings("unused")
private static String getCharsetFromResponse(HttpResponse ressponse) {
// Content-Type:text/html; charset=GBK
if (ressponse.getEntity() != null && ressponse.getEntity().getContentType() != null && ressponse.getEntity().getContentType().getValue() != null) {
String contentType = ressponse.getEntity().getContentType().getValue();
if (contentType.contains("charset=")) {
return contentType.substring(contentType.indexOf("charset=") + 8);
}
}
return null;
}
/**
* 创建 SSL连接
*
* @return
* @throws GeneralSecurityException
*/
private static CloseableHttpClient createSSLInsecureClient() throws GeneralSecurityException {
try {
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
@Override
public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
return true;
}
}).build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, new X509HostnameVerifier() {
@Override
public boolean verify(String arg0, SSLSession arg1) {
return true;
}
@Override
public void verify(String host, SSLSocket ssl)
throws IOException {
}
@Override
public void verify(String host, X509Certificate cert)
throws SSLException {
}
@Override
public void verify(String host, String[] cns,
String[] subjectAlts) throws SSLException {
}
});
return HttpClients.custom().setSSLSocketFactory(sslsf).build();
} catch (GeneralSecurityException e) {
throw e;
}
}
public static void main(String[] args) {
try {
String str = post("https://localhost:443/ssl/test.shtml", "name=12&page=34", "application/x-www-form-urlencoded", "UTF-8", 10000, 10000);
//String str= get("https://localhost:443/ssl/test.shtml?name=12&page=34","GBK");
/*Map<String,String> map = new HashMap<String,String>();
map.put("name", "111");
map.put("page", "222");
String str= postForm("https://localhost:443/ssl/test.shtml",map,null, 10000, 10000);*/
System.out.println(str);
} catch (ConnectTimeoutException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SocketTimeoutException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
5.3 所在流程位置
Controller层
在生成微信二维码中设置的重定向url就是来调用该方法,参数code,status都是成功扫描二维码,确认授权后获得的参数
@ApiOperation("获得扫码人信息")
@GetMapping("callback")
public String callback(String code,String status){
String token = wxApiService.callback(code,status);
return "redirect:http://localhost:3000?token=" + token;
}
Service层
/**
*获得扫码用户信息
*
* @param code 扫码成功生成的code
* @param status
* @return
*/
public String callback(String code, String status) {
String token = "";
try {
//通过code获取access_token 访问固定网站
//参数说明
/*
appid:应用唯一标识,在微信开放平台提交应用审核通过后获得
secret:应用密钥AppSecret,在微信开放平台提交应用审核通过后获得
code:填写第一步(扫描二维码登录成功返回的code)获取的code参数
grant_type:填authorization_code
* */
String url = "https://api.weixin.qq.com/sns/oauth2/access_token?" + "appid=%s" +
"&secret=%s" +
"&code=%s" +
"&grant_type=authorization_code";
//填写参数
url = String.format(url, ConstantWxUtils.APP_ID, ConstantWxUtils.APP_SECRET, code);
//使用工具类发送请求
String accessTokenInfo = HttpClientUtils.get(url);
/*
accessTokenInfo结果集:
access_token 接口调用凭证
expires_in
access_token接口调用凭证超时时间,单位(秒)
refresh_token 用户刷新access_token
openid
授权用户唯一标识
scope
用户授权的作用域,使用逗号(,)分隔
unionid
当且仅当该网站应用已获得该用户的userinfo授权时,才会出现该字段。
* */
//使用json转换工具将结果集转换为map
Gson gson = new Gson();
HashMap<String, String> map = gson.fromJson(accessTokenInfo, HashMap.class);
String accessToken = map.get("access_token");
String openid = map.get("openid");
QueryWrapper<UcenterMember> wrapper = new QueryWrapper<>();
wrapper.eq("openid", openid);
UcenterMember member = memberService.getOne(wrapper);
//根据openid查询记录 查看是否有该用户微信登录的信息 若没有就记录
if (member == null) {
//通过access_token调用接口获取用户个人信息 (前提:1. access_token有效且未超时 2. 微信用户已授权给第三方应用帐号相应接口作用域(scope)。)
String wxUrl = "https://api.weixin.qq.com/sns/userinfo?" + "access_token=%s" +
"&openid=%s";
wxUrl = String.format(wxUrl, accessToken, openid);
String memberInfo = HttpClientUtils.get(wxUrl);
/*
openid 普通用户的标识,对当前开发者帐号唯一
nickname 普通用户昵称
sex 普通用户性别,1为男性,2为女性
province 普通用户个人资料填写的省份
city 普通用户个人资料填写的城市
country 国家,如中国为CN
headimgurl 用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),用户没有头像时该项为空
privilege 用户特权信息,json数组,如微信沃卡用户为(chinaunicom) unionid 用户统一标识。针对一个微信开放平台帐号下的应用,同一用户的unionid是唯一的。 */
HashMap<String, String> infoMap = gson.fromJson(memberInfo, HashMap.class); String nickname = infoMap.get("nickname");
String headimgurl = infoMap.get("headimgurl");
member = new UcenterMember();
member.setOpenid(openid);
member.setNickname(nickname);
member.setAvatar(headimgurl);
memberService.save(member);
}
token = JwtUtils.getJwtToken(member.getId(), member.getNickname()); } catch (Exception e) {
e.printStackTrace();
throw new ServerException("登陆失败");
}
return token;
}
@ApiOperation("获得扫码人信息")
@GetMapping("callback")
public String callback(String code,String status){
String token = wxApiService.callback(code,status);
return "redirect:http://localhost:3000?token=" + token;
}
6、测试
可以看到我们频接了对应的url,拿到这个url后,我们在打开一个新的页面去访问,就会生成二位码,只要你的appid和和回调地址以及appsecret是真实的,就能出现二位码,如果不存在,会提示你appid参数错误等信息
完成!