引言

这几天在研究微信登录,今天解决了获取微信二维码问题;在这里总结一下

关于微信登录想说的话

第一次接触微信登录,开始就弄混了登录方式;简单来说,微信扫码登录分为两种,一种是微信公众平台,一种是微信开放平台,两者的开发文档也不一样,开始就是一直用的微信公众号的参数却使用的是微信开放平台提供的接口,找了半天问题;


总结一下:

微信公众号(公众平台) 和 微信开放平台 是两码事。

  • 微信公众平台是扫码通过微信公众号授权登录的,个人用户可以申请订阅号,但是没有一些接口调用权限,企业用户可以申请服务号,有许多接口权限;但是个人用户可以通过测试号获取一些权限进行学习测试;
  • 微信开放平台是微信为了接入更多第三方应用而开放的接口,对于web应用,可以申请web应用,从而获取权限,但是只能是企业用户才能申请;(个人学习很不方便)

两者开发文档是不同的,所以看网上教程一定要看清楚是公众平台还是开放平台,不要跟错教程了;最好就是看官方文档,这样就能避免踩坑;(我开始就是看不下去文档,因为微信是php示例代码,所以一直看网上教程,就被各种各样的教程弄晕了;最后还是乖乖看文档去了)


下面就进入正题

准备环境

首先申请了一个公众号,然后从开发者工具中进入测试号:

然后就是这个界面:

appID和appsecret都是操作所需参数;

接口配置下面介绍;

接口配置

接口配置后面会用到,所以需要先配置一下,其实看官方文档就能看懂,但是示例代码是php,所以这里我来演示一下我的操作;

官方文档:传送门

官方文档意思就是你得有一个域名,代码还得跑在该域名下的服务器上,然后你自己需要再代码中实现一个接口获取微信发来的信息进行处理;

如果你和我一样是个学生,开发都在本地127.0.0.1,或者没有域名,那该怎么办?因为微信不能直接调用本地ip,这就需要用到内网穿透;

简单来说就是微信想要向你填入的url发送请求数据,但是它不能直接向本地127.0.0.1发送,我们可以通过内网穿透获取一个域名,让该域名映射到本地127.0.0.1,然后微信向该域名发送数据,这样就把数据发送到了本地;(个人理解)


内网穿透

我通过ngrok进行的内网穿透,就花2块钱实名了一下,然后有一个免费的隧道可以使用:

然后进行配置,它就会給你分一个域名:

接下来下载ngrok客户端,启动隧道:

点击.bat文件,按要求输入隧道id:

链接成功,这时时就意味着访问本地127.0.0.1和访问生成的域名的效果是一样的;


访问流程就是:

文档:

微信向自己填的url发送请求;

实现接入代码

然后就是代码配置,就是官网文档的第二步,官方检验是一个php代码,下面是java代码:

controller接口:

/***  接入微信接口*/
@GetMapping("/callback")
@ResponseBody
public String checkSign (HttpServletRequest request) throws Exception {log.info("===========>checkSign");// 获取微信请求参数String signature = request.getParameter ("signature");String timestamp = request.getParameter ("timestamp");String nonce = request.getParameter ("nonce");String echostr = request.getParameter ("echostr");log.info("开始校验此次消息是否来自微信服务器,param->signature:{},\ntimestamp:{},\nnonce:{},\nechostr:{}",signature, timestamp, nonce, echostr);if (CheckWXTokenUtils.checkSignature(signature, timestamp, nonce)) {return echostr;}return "";
}

校验工具类(直接cv)

import lombok.extern.log4j.Log4j2;import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;/*** 和微信建立链接参数校验*/
@Log4j2
public class CheckWXTokenUtils {private static final String TOKEN = "xxxxxx"; // 自定义的token/*** 校验微信服务器Token签名** @param signature 微信加密签名* @param timestamp 时间戳* @param nonce     随机数* @return boolean*/public static boolean checkSignature(String signature, String timestamp, String nonce) {String[] arr = {TOKEN, timestamp, nonce};Arrays.sort(arr);StringBuilder stringBuilder = new StringBuilder();for (String param : arr) {stringBuilder.append(param);}String hexString = SHA1(stringBuilder.toString());return signature.equals(hexString);}private static String SHA1(String str) {MessageDigest md;try {md = MessageDigest.getInstance("SHA-1");byte[] digest = md.digest(str.getBytes());return toHexString(digest);} catch (NoSuchAlgorithmException e) {log.info("校验令牌Token出现错误:{}", e.getMessage());}return "";}/*** 字节数组转化为十六进制** @param digest 字节数组* @return String*/private static String toHexString(byte[] digest) {StringBuilder hexString = new StringBuilder();for (byte b : digest) {String shaHex = Integer.toHexString(b & 0xff);if (shaHex.length() < 2) {hexString.append(0);}hexString.append(shaHex);}return hexString.toString();}
}

几个校验参数官方文档也说了,自己对比着看就行了;

再次放入官方文档地址:接入概述


配置就是:

url是内网穿透域名+自己实现的接口

token也是自己写的;

然后在网页服务中:

点击修改:(还是内网穿透域名)

然后就配置好了;

下面就是正式开始二维码生成了;

生成二维码

先放出文档:

生成带参数的二维码

获取Access token

获取ticket

我们先看第一个文档,大致意思就是先发送一个获取二维码ticket的post请求,获取ticket:

整理一下:

url: post
https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=TOKEN参数:
param参数:access_token
json参数(两个必要的,其他可以自己看文档加): {"expire_seconds": 604800, "action_name": "QR_SCENE"}结果:
{"ticket":"gQH47joAAAAAAAAAASxodHRwOi8vd2VpeGluLnFxLmNvbS9xL2taZ2Z3TVRtNzJXV1Brb3ZhYmJJAAIEZ23sUwMEmm
3sUw==","expire_seconds":60,"url":"http://weixin.qq.com/q/kZgfwMTm72WWPkovabbI"}

那么就很简单了,就是调用就接口获取ticket,但是可以发现所需要的参数中的access_token我们并没有,所以调用该接口前需要先获取,同样也有官方文档,上面已经放出来了;


获取access_token

同样整理一下:

url: get
https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET参数:
grant_type、appid、secret结果:
{"access_token":"ACCESS_TOKEN","expires_in":7200}

其中grant_type就写client_credential就行了,固定的;

appid和secret就是测试号的:

所以很轻易就可以获取到access_token;

获取到access_token就可以获取ticket,到这里这两步就完成了;

接下来就是获取二维码了:

获取二维码

这就不过多介绍了,就加一个ticket参数即可;

测试流程

下面我使用postman测试一遍流程:

1,首先获取access_token:

https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=&secret=

2,然后获取ticket:

https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=ACCESS_TOKEN

3,最后获取二维码:

https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=TICKET

直接浏览器访问该链接,即可看到该二维码

整个流程就走通了;

下面就是代码实现该流程;

代码实现

这里思考一个问题,后端内部如何自己发送请求获取响应?

平时都是前端向后端发送请求,后端响应;

因为我们需要先发送请求获取access_token,再获取ticket,最后响应给前端的就是一个二维码url,所以后端需要自己发请求并获取响应结果;

这里可以使用httpclient,具体细节可以查资料,这里不过多介绍;

下面是实现代码:

httpclient依赖:

<dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId><version>4.5.13</version>
</dependency>

httpclient工具类(直接cv):

import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;import javax.net.ssl.SSLContext;
import java.io.File;
import java.io.FileInputStream;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;/*** HttpClient工具类*/
public class HttpClientUtils {private static final CloseableHttpClient httpClient;// 采用静态代码块,初始化超时时间配置,再根据配置生成默认httpClient对象static {RequestConfig config = RequestConfig.custom().setConnectTimeout(30000).setSocketTimeout(15000).build();httpClient = HttpClientBuilder.create().setDefaultRequestConfig(config).build();}/*** 发送 HTTP GET请求,不带请求参数和请求头* @param url 请求地址* @return* @throws Exception*/public static String doGet(String url) throws Exception {HttpGet httpGet = new HttpGet(url);return doHttp(httpGet);}/*** 发送 HTTP GET,请求带参数,不带请求头* @param url 请求地址* @param params 请求参数* @return* @throws Exception*/public static String doGet(String url, Map<String, Object> params) throws Exception {// 转换请求参数List<NameValuePair> pairs = covertParamsToList(params);// 装载请求地址和参数URIBuilder ub = new URIBuilder();ub.setPath(url);ub.setParameters(pairs);HttpGet httpGet = new HttpGet(ub.build());return doHttp(httpGet);}/*** 发送 HTTP GET请求,带请求参数和请求头* @param url 请求地址* @param headers 请求头* @param params 请求参数* @return* @throws Exception*/public static String doGet(String url, Map<String, Object> headers, Map<String, Object> params) throws Exception {// 转换请求参数List<NameValuePair> pairs = covertParamsToList(params);// 装载请求地址和参数URIBuilder ub = new URIBuilder();ub.setPath(url);ub.setParameters(pairs);HttpGet httpGet = new HttpGet(ub.build());// 设置请求头for (Map.Entry<String, Object> param : headers.entrySet()) {httpGet.addHeader(param.getKey(), String.valueOf(param.getValue()));}return doHttp(httpGet);}/*** 发送 HTTP POST请求,不带请求参数和请求头** @param url 请求地址* @return* @throws Exception*/public static String doPost(String url) throws Exception {HttpPost httpPost = new HttpPost(url);return doHttp(httpPost);}/*** 发送 HTTP POST请求,带请求参数,不带请求头** @param url    请求地址* @param params 请求参数* @return* @throws Exception*/public static String doPost(String url, Map<String, Object> params) throws Exception {// 转换请求参数List<NameValuePair> pairs = covertParamsToList(params);HttpPost httpPost = new HttpPost(url);// 设置请求参数httpPost.setEntity(new UrlEncodedFormEntity(pairs, StandardCharsets.UTF_8.name()));return doHttp(httpPost);}/*** 发送 HTTP POST请求,带请求参数和请求头** @param url     地址* @param headers 请求头* @param params  参数* @return* @throws Exception*/public static String doPost(String url, Map<String, Object> headers, Map<String, Object> params) throws Exception {// 转换请求参数List<NameValuePair> pairs = covertParamsToList(params);HttpPost httpPost = new HttpPost(url);// 设置请求参数httpPost.setEntity(new UrlEncodedFormEntity(pairs, StandardCharsets.UTF_8.name()));// 设置请求头for (Map.Entry<String, Object> param : headers.entrySet()) {httpPost.addHeader(param.getKey(), String.valueOf(param.getValue()));}return doHttp(httpPost);}/*** 发送 HTTP POST请求,请求参数是JSON格式,数据编码是UTF-8** @param url 请求地址* @param param 请求参数* @return* @throws Exception*/public static String doPostJson(String url, String param) throws Exception {HttpPost httpPost = new HttpPost(url);// 设置请求头httpPost.addHeader("Content-Type", "application/json; charset=UTF-8");// 设置请求参数httpPost.setEntity(new StringEntity(param, StandardCharsets.UTF_8.name()));return doHttp(httpPost);}/*** 发送 HTTP POST请求,请求参数是XML格式,数据编码是UTF-8** @param url 请求地址* @param param 请求参数* @return* @throws Exception*/public static String doPostXml(String url, String param) throws Exception {HttpPost httpPost = new HttpPost(url);// 设置请求头httpPost.addHeader("Content-Type", "application/xml; charset=UTF-8");// 设置请求参数httpPost.setEntity(new StringEntity(param, StandardCharsets.UTF_8.name()));return doHttp(httpPost);}/*** 发送 HTTPS POST请求,使用指定的证书文件及密码,不带请求头信息<** @param url 请求地址* @param param 请求参数* @param path 证书全路径* @param password 证书密码* @return* @throws Exception* @throws Exception*/public static String doHttpsPost(String url, String param, String path, String password) throws Exception {HttpPost httpPost = new HttpPost(url);// 设置请求参数httpPost.setEntity(new StringEntity(param, StandardCharsets.UTF_8.name()));return doHttps(httpPost, path, password);}/*** 发送 HTTPS POST请求,使用指定的证书文件及密码,请求头为“application/xml;charset=UTF-8”** @param url 请求地址* @param param 请求参数* @param path 证书全路径* @param password 证书密码* @return* @throws Exception* @throws Exception*/public static String doHttpsPostXml(String url, String param, String path, String password) throws Exception {HttpPost httpPost = new HttpPost(url);// 设置请求头httpPost.addHeader("Content-Type", "application/xml; charset=UTF-8");// 设置请求参数httpPost.setEntity(new StringEntity(param, StandardCharsets.UTF_8.name()));return doHttps(httpPost, path, password);}/*** 发送 HTTPS 请求,使用指定的证书文件及密码** @param request* @param path 证书全路径* @param password 证书密码* @return* @throws Exception* @throws Exception*/private static String doHttps(HttpRequestBase request, String path, String password) throws Exception {// 获取HTTPS SSL证书SSLConnectionSocketFactory csf = getHttpsFactory(path, password);// 通过连接池获取连接对象CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(csf).build();return doRequest(httpClient, request);}/*** 获取HTTPS SSL连接工厂,使用指定的证书文件及密码** @param path     证书全路径* @param password 证书密码* @return* @throws Exception* @throws Exception*/private static SSLConnectionSocketFactory getHttpsFactory(String path, String password) throws Exception {// 初始化证书,指定证书类型为“PKCS12”KeyStore keyStore = KeyStore.getInstance("PKCS12");// 读取指定路径的证书FileInputStream input = new FileInputStream(new File(path));try {// 装载读取到的证书,并指定证书密码keyStore.load(input, password.toCharArray());} finally {input.close();}// 获取HTTPS SSL证书连接上下文SSLContext sslContext = SSLContexts.custom().loadKeyMaterial(keyStore, password.toCharArray()).build();// 获取HTTPS连接工厂,指定TSL版本SSLConnectionSocketFactory sslCsf = new SSLConnectionSocketFactory(sslContext, new String[]{"SSLv2Hello", "SSLv3", "TLSv1", "TLSv1.2"}, null, SSLConnectionSocketFactory.getDefaultHostnameVerifier());return sslCsf;}/*** 发送 HTTP 请求** @param request* @return* @throws Exception*/private static String doHttp(HttpRequestBase request) throws Exception {// 通过连接池获取连接对象return doRequest(httpClient, request);}/*** 处理Http/Https请求,并返回请求结果,默认请求编码方式:UTF-8* @param httpClient* @param request* @return*/private static String doRequest(CloseableHttpClient httpClient, HttpRequestBase request) throws Exception {String result = null;try (CloseableHttpResponse response = httpClient.execute(request)) {// 获取请求结果int statusCode = response.getStatusLine().getStatusCode();if (statusCode != 200) {request.abort();throw new RuntimeException("HttpClient error status code: " + statusCode);}// 解析请求结果HttpEntity entity = response.getEntity();// 转换结果result = EntityUtils.toString(entity, StandardCharsets.UTF_8.name());// 关闭IO流EntityUtils.consume(entity);}return result;}/*** 转换请求参数,将Map键值对拼接成QueryString字符串** @param params* @return*/public static String covertMapToQueryStr(Map<String, Object> params) {List<NameValuePair> pairs = covertParamsToList(params);return URLEncodedUtils.format(pairs, StandardCharsets.UTF_8.name());}/*** 转换请求参数** @param params* @return*/public static List<NameValuePair> covertParamsToList(Map<String, Object> params) {List<NameValuePair> pairs = new ArrayList<>();for (Map.Entry<String, Object> param : params.entrySet()) {pairs.add(new BasicNameValuePair(param.getKey(), String.valueOf(param.getValue())));}return pairs;}
}

然后controller实现一个接口,前端调用该接口获取二维码url:

// 获取二维码
@GetMapping("/qr/login/param")
@ResponseBody
public BaseResponse<String> getWxQRCodeParam() {String QRUrl = null;try {// 第一步:发送请求获取access_tokenString getAccessTokenUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential" +"&appid=" + WXConstant.APP_ID +"&secret=" + WXConstant.APP_SECRET;String accessTokenRes = HttpClientUtils.doGet(getAccessTokenUrl);log.info("accessTokenRes=>" + accessTokenRes);String accessToken = (String) JSON.parseObject(accessTokenRes).get("access_token"); // 获取到access_token// 第二步:通过access_token和一些参数发送post请求获取二维码TicketString getTicketUrl = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=" + accessToken;// 封装参数Map<String, Object> ticketInfo = new HashMap<>();ticketInfo.put("expire_seconds", 604800); // 二维码超时时间ticketInfo.put("action_name", "QR_SCENE");String ticketJsonInfo = JSON.toJSON(ticketInfo).toString();String ticketRes = HttpClientUtils.doPostJson(getTicketUrl, ticketJsonInfo);log.info("ticketRes=>" + ticketRes);String ticket = (String) JSON.parseObject(ticketRes).get("ticket");// 第三步:通过ticket获取二维码urlString encodeTicket = URLEncoder.encode(ticket, "utf-8"); // 编码ticketString getQRUrl = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=" + encodeTicket;QRUrl = getQRUrl; // 二维码url} catch (Exception e) {e.printStackTrace();}return ResultUtils.success(QRUrl);
}

注:该接口只注重了功能实现,具体安全性并未考虑;

可以调用该接口测试一下:

那么前端只需要获取到这个url并展示出来就行了:

可以测试一下:

可以看到每次都能获取到不同二维码;


到这里二维码生成就完成了,可以扫码测试一下:

扫码候就是公众号界面了,接下来就是一些授权操作了,下集在讲;

总结

总的来说只要细心看文档还不算是特别难理解的,所以一定要多看文档!

总的来说流程就是那三步:

因为文章是本人理解记录的,可能会有错误,有错误问题欢迎交流!

微信公众号扫码登录(一)—— 获取微信公众号二维码相关推荐

  1. 微信公众号怎么生成带粉丝关注统计的渠道二维码

    关于微信公众号在线生成带粉丝关注统计的渠道二维码,第三方工具微号帮提供了渠道二维码生成功能实现,可以为公众号生成带粉丝关注统计的渠道二维码,可以生成多个不同的渠道二维码,每个渠道二维码都可以单独统计粉 ...

  2. 微信公众号开发之生成并扫描带参数的二维码(无需改动)

    首先把参考的博文罗列出来: 1.微信公众号开发之生成并扫描带参数的二维码: https://blog.csdn.net/qq_23543983/article/details/80228558 2.由 ...

  3. 微信公众号开发之生成并扫描带参数的二维码

    生成带参数二维码详见微信公众平台之生成带参数的二维码 具体步骤:可在微信测试平台https://mp.weixin.qq.com/debug进行生成 生成结果如下: 拿到ticket 请求 https ...

  4. 获取微信code、调用小程序云函数、获取小程序支付二维码、小程序授权、小程序获取SeesionId

    获取微信code.调用小程序云函数.获取小程序支付二维码.小程序授权.小程序获取SeesionId 只需要调用对应的接口就行了 1. 小程序code获取 基本信息 接口状态: 已完成 接口URL: h ...

  5. 获取生成和解析二维码的Jar包(QRCode方式)

    由于QRCode方式生成二维码和解析二维码不在一个Jar包内,而且获取的网址也不一样,因此我们需要去两个网址获取这两个Jar包. 第一步:获取生成二维码的Jar包 首先是获取生成QRCode二维码的J ...

  6. 【绝对靠谱】Vue生成二维码Qrcode,可插入二维码中心logo图标,可以设置二维码颜色大小等属性

    效果 代码 npm install vue-qr --save 个别网络用 cnpm install vue-qr --save 安装 <template><div class=&q ...

  7. Swift开发小技巧--扫描二维码,二维码的描边与锁定,设置扫描范围,二维码的生成(高清,无码,你懂得!)...

    二维码的扫描,二维码的锁定与描边,二维码的扫描范围,二维码的生成(高清,无码,你懂得!),识别相册中的二维码 扫描二维码用到的三个重要对象的关系,如图: 1.懒加载各种类 // MARK: - 懒加载 ...

  8. 手机QQ浏览器“有码女神”惊现!意在推广二维码

    近日,新浪微博.腾讯微博.天涯论坛.猫扑上关于"有码女神"的帖子满天飞,三个身上贴着二维码的模特女瞬间走红网络.很多网友认为这是一场有策划的营销活动,但是具体是谁策划的,由于没法现 ...

  9. 互联网 4 大发明之二维码,你如何使用 Python 生成二维码?

    阅读文本大概需要 8 分钟. 新时代,人们有人信新的追求,自然而然会有新发明的诞生.去年,在"一带一路"国际合作高峰论坛举行期间, 20 国青年投票选出中国的"新四大发明 ...

  10. 使用zxing生成带logo的二维码图片,自动调节logo图片相对二维码图片的大小

    使用zxing生成带logo的二维码图片,自动调节logo图片相对二维码图片的大小  * 可选是否带logo,可选是否保存二维码图片:结果返回base64编码的图片数据字符串  * 页面显示:< ...

最新文章

  1. 一起学spring--spring事件机制--监听器
  2. python如何输入数字赋值_三、python语法(定义,赋值,注释,输入输出)
  3. 为什么查询sap数据库表的时候前面要加sapsr3
  4. clientHeight、offsetHeight 和 scrollHeight
  5. C++之用std::nothrow分配内存失败不抛异常
  6. linux make指定目标平台,CMake on Linux:目标平台不支持动态链接
  7. 在Keil中利用AStyle插件格式化代码
  8. 天梯—重要的话说三遍(C语言)
  9. 智能家居的新篇章-PHILIPS HUE
  10. Element-ui中form组件的校验规则
  11. CodeLite12.0.0 MinGW 8.1.0 安装配置
  12. matlab spline边界约束,三阶样条插值(一阶导数边界条件) matlab程序
  13. clover写入efi_把clover复制到EFI之后电脑不认clover的解决方法(UEFI Shell强制写入)
  14. 文章学习(一)方法类:利用CNN从专利图像导出设计特征向量
  15. 一款客服系统有哪些必备的功能模块?
  16. vscode代码格式化、vetur代码格式化取消末尾添加逗号
  17. lumia 525 android 7.1,给大神跪了!诺基亚Lumia 520成功刷上安卓7.1
  18. SEO白帽必备孙子兵法之三十六计
  19. 「近世代數概論」(Garrett Birkhoff,Saunders Mac Lane) 3.1.1 引理2
  20. 《程序员》 -- 说说软件专利

热门文章

  1. python3视频教程哪个好_python3入门视频教程
  2. 数据库原理及应用教程第四版课后答案
  3. GNS3中下载路由器
  4. 微信小程序全国城市搜索(可进行城市中文拼音首字母搜索)
  5. 《数学分析》收敛数列的性质
  6. 【资料】《模拟电子技术基础-童诗白第四版》
  7. win10新建文件夹必须刷新才能显示
  8. 未来教育 ***java二级考试题库第二十五套错题***
  9. JAVA爬虫系列教程
  10. 正解:《Java 程序员的黄金 5 年》看完我才知道当时有多蠢