前言


  本打算用CountDownLatch来实现,但有个问题我没有考虑,就是当用户APP没有扫二维码的时候,线程会阻塞5分钟,这反而造成性能的下降。好吧,现在回归传统方式:前端ajax每隔1秒或2秒发一次请求,去查询后端的登录状态。

一、支付宝和微信的实现方式


1.支付宝的实现方式

每隔1秒会发起一次http请求,调用https://securitycore.alipay.com/barcode/barcodeProcessStatus.json?securityId=web%7Cauthcenter_qrcode_login%7C【UUID】&_callback=light.request._callbacks.callback3

如果获取到认证信息,则跳转进入内部系统。

如图所示

2.微信的实现方式

每隔1分钟调用一次 https://login.wx.qq.com/cgi-bin/mmwebwx-bin/login?loginicon=true&uuid=【UUID】&tip=0&r=-1524754438&_=1521943100181

而请求一次的时间预计是1分钟,如果没有查到到认证信息,则会返回

window.code=408;

  

没有扫码就会一直等待。当一定时间不扫码二维码,页面就会强制刷新。

我猜想后端的机制和我上篇《spring boot高性能实现二维码扫码登录(上)——单服务器版》类似。

那么如果用户长时间不扫二维码,服务器的线程将不会被唤醒,微信是怎么做到高性能的。如果有园友知道,可以给我留言。

3.我的实现方式

好了,我这里选用支付宝的实现方式。因为简单粗暴,还高效。

流程如下:

1.前端发起成二维码的请求,并得到登录UUID

2.后端生成UUID后写入Redis。

3.前端每隔1秒发起一次请求,从Redis中获取认证信息,如果没有认证信息则返回waiting状态,如果查询到认证信息,则将认证信息写入seesion。

二、代码编写


pom.xml引入Redis及Session的依赖:

        <!-- redis --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- session --><dependency><groupId>org.springframework.session</groupId><artifactId>spring-session-data-redis</artifactId></dependency>

  

完整的pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.demo</groupId><artifactId>auth</artifactId><version>0.0.1-SNAPSHOT</version><packaging>jar</packaging><name>auth</name><description>二维码登录</description><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.0.0.RELEASE</version><relativePath /> <!-- lookup parent from repository --></parent><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!-- zxing --><dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.3.0</version></dependency><dependency><groupId>com.google.zxing</groupId><artifactId>javase</artifactId><version>3.3.0</version></dependency><!-- redis --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- session --><dependency><groupId>org.springframework.session</groupId><artifactId>spring-session-data-redis</artifactId></dependency><dependency><groupId>commons-codec</groupId><artifactId>commons-codec</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>

pom.xml

App.java入口类:

package com.demo.auth;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class App {public static void main(String[] args) {SpringApplication.run(App.class, args);}}

App.java

resources/application.properties 中配置使用redis存储session

# session
spring.session.store-type=redis

前端页面index.html和login.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>二维码登录</title>
</head>
<body><h1>二维码登录</h1><h4><a target="_blank" href="http://www.cnblogs.com/GoodHelper/">from刘冬的博客</a></h4><h3 th:text="'登录用户:' + ${user}"></h3><br /><a href="/logout">注销</a>
</body>
</html>

index.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>二维码登录</title>
<script src="//cdn.bootcss.com/angular.js/1.5.6/angular.min.js"></script>
<script type="text/javascript">/*<![CDATA[*/var app = angular.module('app', []);app.controller('MainController', function($rootScope, $scope, $http) {//二维码图片src
        $scope.src = null;//获取二维码
        $scope.getQrCode = function() {$http.get('/login/getQrCode').success(function(data) {if (!data || !data.loginId || !data.image)return;$scope.src = 'data:image/png;base64,' + data.image$scope.getResponse(data.loginId)});}//获取登录响应
        $scope.getResponse = function(loginId) {$http.get('/login/getResponse/' + loginId).success(function(data) {if (!data) {setTimeout($scope.getQrCode(), 1000);return;}//一秒后,重新获取登录二维码if (!data.success) {if (data.stats == 'waiting') {//一秒后再次调用
                        setTimeout(function() {$scope.getResponse(loginId);}, 1000);} else {//重新获取二维码
                        setTimeout(function() {$scope.getQrCode(loginId);}, 1000);}return;}//登录成功,进去首页
                location.href = '/'}).error(function(data, status) {//一秒后,重新获取登录二维码
                setTimeout(function() {$scope.getQrCode(loginId);}, 1000);})}

        $scope.getQrCode();});/*]]>*/
</script>
</head>
<body ng-app="app" ng-controller="MainController"><h1>扫码登录</h1><h4><a target="_blank" href="http://www.cnblogs.com/GoodHelper/">from刘冬的博客</a></h4><img ng-show="src" ng-src="{{src}}" />
</body>
</html>

bean配置类BeanConfig.java:

package com.demo.auth;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.StringRedisTemplate;@Configuration
public class BeanConfig {@Beanpublic StringRedisTemplate template(RedisConnectionFactory connectionFactory) {return new StringRedisTemplate(connectionFactory);}}

登录处理类:

/*** 登录配置 博客出处:http://www.cnblogs.com/GoodHelper/**/
@Configuration
public class WebSecurityConfig implements WebMvcConfigurer {/*** 登录session key*/public final static String SESSION_KEY = "user";@Beanpublic SecurityInterceptor getSecurityInterceptor() {return new SecurityInterceptor();}public void addInterceptors(InterceptorRegistry registry) {InterceptorRegistration addInterceptor = registry.addInterceptor(getSecurityInterceptor());// 排除配置addInterceptor.excludePathPatterns("/error");addInterceptor.excludePathPatterns("/login");addInterceptor.excludePathPatterns("/login/**");// 拦截配置addInterceptor.addPathPatterns("/**");}private class SecurityInterceptor extends HandlerInterceptorAdapter {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {HttpSession session = request.getSession();if (session.getAttribute(SESSION_KEY) != null)return true;// 跳转登录String url = "/login";response.sendRedirect(url);return false;}}
}

WebSecurityConfig

MainController类修改为:

package com.demo.auth;import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;import javax.imageio.ImageIO;
import javax.servlet.http.HttpSession;import org.apache.commons.codec.binary.Base64;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.SessionAttribute;import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;/*** 控制器* * @author 刘冬博客http://www.cnblogs.com/GoodHelper**/
@Controller
public class MainController {private static final String LOGIN_KEY = "key.value.login.";@Autowiredprivate StringRedisTemplate redisTemplate;@GetMapping({ "/", "index" })public String index(Model model, @SessionAttribute(WebSecurityConfig.SESSION_KEY) String user) {model.addAttribute("user", user);return "index";}@GetMapping("login")public String login() {return "login";}/*** 获取二维码* * @return*/@GetMapping("login/getQrCode")public @ResponseBody Map<String, Object> getQrCode() throws Exception {Map<String, Object> result = new HashMap<>();String loginId = UUID.randomUUID().toString();result.put("loginId", loginId);// app端登录地址String loginUrl = "http://localhost:8080/login/setUser/loginId/";result.put("loginUrl", loginUrl);result.put("image", createQrCode(loginUrl));ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();opsForValue.set(LOGIN_KEY + loginId, loginId, 5, TimeUnit.MINUTES);return result;}/*** app二维码登录地址,这里为了测试才传{user},实际项目中user是通过其他方式传值* * @param loginId* @param user* @return*/@GetMapping("login/setUser/{loginId}/{user}")public @ResponseBody Map<String, Object> setUser(@PathVariable String loginId, @PathVariable String user) {ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();String value = opsForValue.get(LOGIN_KEY + loginId);if (value != null) {opsForValue.set(LOGIN_KEY + loginId, user, 1, TimeUnit.MINUTES);}Map<String, Object> result = new HashMap<>();result.put("loginId", loginId);result.put("user", user);return result;}/*** 等待二维码扫码结果的长连接* * @param loginId* @param session* @return*/@GetMapping("login/getResponse/{loginId}")public @ResponseBody Map<String, Object> getResponse(@PathVariable String loginId, HttpSession session) {Map<String, Object> result = new HashMap<>();result.put("loginId", loginId);ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();String user = opsForValue.get(LOGIN_KEY + loginId);// 长时间不扫码,二维码失效。需重新获二维码if (user == null) {result.put("success", false);result.put("stats", "refresh");return result;}// 登录扫码二维码if (user.equals(loginId)) {result.put("success", false);result.put("stats", "waiting");return result;}// 登录成,认证信息写入session
        session.setAttribute(WebSecurityConfig.SESSION_KEY, user);result.put("success", true);result.put("stats", "ok");return result;}/*** 生成base64二维码* * @param content* @return* @throws Exception*/private String createQrCode(String content) throws Exception {try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {Hashtable<EncodeHintType, Object> hints = new Hashtable<EncodeHintType, Object>();hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);hints.put(EncodeHintType.CHARACTER_SET, "utf-8");hints.put(EncodeHintType.MARGIN, 1);BitMatrix bitMatrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, 400, 400, hints);int width = bitMatrix.getWidth();int height = bitMatrix.getHeight();BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);for (int x = 0; x < width; x++) {for (int y = 0; y < height; y++) {image.setRGB(x, y, bitMatrix.get(x, y) ? 0xFF000000 : 0xFFFFFFFF);}}ImageIO.write(image, "JPG", out);return Base64.encodeBase64String(out.toByteArray());}}@GetMapping("/logout")public String logout(HttpSession session) {// 移除session
        session.removeAttribute(WebSecurityConfig.SESSION_KEY);return "redirect:/login";}
}

三、运行效果: 


如图所示,效果与上篇一样。

目前我在考虑微信的方式。我打算采用 CountDownLatch await一分钟,然后使用消息订阅+广播唤醒线程的方式来实现此功能。如果有懂原理的朋友可以给我留言。

代码下载

如果你觉得我的博客对你有帮助,可以给我点儿打赏,左侧微信,右侧支付宝。

有可能就是你的一点打赏会让我的博客写的更好:)

返回玩转spring boot系列目录

作者:刘冬.NET 博客地址:http://www.cnblogs.com/GoodHelper/ 欢迎转载,但须保留版权

转载于:https://www.cnblogs.com/GoodHelper/p/8643071.html

spring boot高性能实现二维码扫码登录(中)——Redis版相关推荐

  1. 【Redis版】spring boot高性能实现二维码扫码登录(中)

    作者: 刘冬 来源:http://www.cnblogs.com/GoodHelper/p/8643071.html 前言 本打算用CountDownLatch来实现,但有个问题我没有考虑,就是当用户 ...

  2. 【订阅与发布机制版】spring boot高性能实现二维码扫码登录(下)

    点击上方[JAVA乐园],选择"置顶公众号",有内涵有价值的文章第一时间送达! 作者: 刘冬 来源:https://www.cnblogs.com/GoodHelper/p/865 ...

  3. spring boot高性能实现二维码扫码登录(上)——单服务器版

    前言 目前网页的主流登录方式是通过手机扫码二维码登录.我看了网上很多关于扫码登录博客后,发现基本思路大致是:打开网页,生成uuid,然后长连接请求后端并等待登录认证相应结果,而后端每个几百毫秒会循环查 ...

  4. 【单服务器版】spring boot高性能实现二维码扫码登录(上)

    作者:刘冬.NET 来源:http://www.cnblogs.com/GoodHelper/p/8641905.html 前言 目前网页的主流登录方式是通过手机扫码二维码登录.我看了网上很多关于扫码 ...

  5. Java(Spring boot)实现生成二维码

    文章目录 一.引入spring boot依赖: 二.工具类代码: 三.调用工具类生成二维码 1.将链接生成二维码图片并保存到指定路径 2.将链接生成二维码直接显示在页面 3.将以get请求传参链接生成 ...

  6. 基于Spring Boot实现电脑端网页微信扫码授权登录方式一(附带完整源码)

    简介 电脑端微信网页扫码授权登录有2种方式: 第一种:基于微信公众号,单独获取登录二维码扫码,然后扫码登录,程序控制跳转逻辑,例如CSDN: 第二种:基于微信开放平台,跳转到微信二维码页面进行扫码登录 ...

  7. Spring Cloud OAuth2 扩展登录方式:帐户密码登录、 手机验证码登录、 二维码扫码登录

    本文扩展了spring security 的登录方式,增长手机验证码登录.二维码登录. 主要实现方式为使用自定义filter. AuthenticationProvider. AbstractAuth ...

  8. PHP实现二维码扫码登录

    实现客户端扫码登录分为下列四步: 1.    Web页面生成二维码 生成的二维码中必须要包含一个用于唯一标识用户的数据,这个唯一标识是为了确保将客户端(手机)与web网页绑定,避免其他人登录了你的账号 ...

  9. C++实现二维码扫码登录

    实现客户端扫码登录分为下列四步: 1.    Web页面生成二维码 生成的二维码中必须要包含一个用于唯一标识用户的数据,这个唯一标识是为了确保将客户端(手机)与web网页绑定,避免其他人登录了你的账号 ...

最新文章

  1. 开放神经网络交换(ONNX)工具
  2. delete file$ recovery----惜分飞
  3. mysql 不要统计null_浅谈为什么Mysql数据库尽量避免NULL
  4. 【★】百度网盘背后的真实策略!
  5. 如何打破线下流量瓶颈?三四线城市实体门店的生存之道
  6. 获取数据库值,再在其值上做修改
  7. 集宁哪有计算机培训班,集宁区有这么一个空间,叫共享自习室
  8. 跨平台的PHP+MySQL_跨平台的PHP+MySQL
  9. 数据库系统工程师考点
  10. 计算机趣味知识竞赛策划,大学生趣味知识竞赛活动的策划书
  11. 部分PTA自测题答案(仅参考)
  12. logback日志集成
  13. php做支付宝接口测试,支付宝接口调试经验总结
  14. MOSES统计机器翻译系统实验过程
  15. ListView实现倒计时功能
  16. 锅炉如何实现物联网云平台方案
  17. 大咖云集,从1到N,第十二届中国IDC产业年度大典集锦
  18. c# ArrayList 和 Hashtable 的使用
  19. 计算机应用技术python_大学计算机应用技术02PythonB2020学习通app网课答案
  20. 虚拟机网络适配器下的3种网络模式(主机模式、桥接模式和NAT模式)

热门文章

  1. 蝙蝠侠遥控器pcb_通过蝙蝠侠从Circle到ML:第一部分
  2. 40%美国人付不起400美元意外开销,大家怎么看?
  3. “ 70后”作家:从尴尬自省到扬眉吐气
  4. SystemVerilog声明的位置
  5. 视频图像处理仿真测试系统
  6. 基于SPI协议的Flash全擦除
  7. qt开发环境 - 简易二进制文件打开,串口自发自收
  8. 语言print如何实现连续输出_【每日一题】如何实现一个高效的单向链表逆序输出?...
  9. html js坐标图,javascript – HTML5 Canvas沿着带坐标的路径拖动图像
  10. 1650显卡学计算机,适合老电脑升级?GTX1650显卡开箱,性价比依然不高!