spring boot高性能實現二維碼掃碼登錄(中)——Redis版

前言


?

  本打算用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

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/252094.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/252094.shtml
英文地址,請注明出處:http://en.pswp.cn/news/252094.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

C# :socket 通訊基礎使用實例

們在講解Socket編程前&#xff0c;先看幾個和Socket編程緊密相關的概念&#xff1a; TCP/IP層次模型當然這里我們只討論重要的四層 01&#xff0c;應用層(Application)&#xff1a;應用層是個很廣泛的概念&#xff0c;有一些基本相同的系統級TCP/IP應用以及應用協議&#xff0…

IBM發表論文:可能已找到處理量子計算退相干的方法

在《自然》雜志最近發表的一篇論文中&#xff0c;IBM和其他機構的研究人員設計了兩種量子算法&#xff0c;利用變分量子電路和量子核估計器來訓練一種支持向量機分類器。這兩種算法背后的關鍵思想是使用量子狀態空間作為特征空間表示&#xff0c;有效地構建映射&#xff0c;從原…

PHP + NGINX 控制視頻文件播放,并防止文件下載

最簡單的方法是使用NGINX的 internal 功能 server { listen 80; server_name www.xxx.com;  location / { index index.php index.html index.htm; root /xxx; if (!-e $request_filename) { rewrite ^/index.php(.*)$ /index.php?s$…

可視化調試工具

rosrun rqt_console rqt_console # 查看日志消息&#xff0c;可filter、highlight指定級別。 rosrun rqt_logger_level rqt_logger_level # 可設在日志記錄器的嚴重級別 rosrun rqt_topic rqt_topic # 顯示topic調試信息 rosrun rqt_publisher rqt_publisher # 在界面中管理ro…

C#:委托基礎與事件

通過以下思維導圖&#xff0c;學習委托的基本概念&#xff0c;后面著重講解委托的運用&#xff0c;希望通過最簡單的方式收獲更多的知識。 1.委托的各種寫法 1、委托 委托名new 委托&#xff08;會調用的方法名); 委托名&#xff08;參數&#xff09;; 2、委托 委托名 會調用…

Git Bash關鍵命令

1.默認目錄是C:\Users\用戶名 2.切換目錄&#xff1a;$cd c:\\windows 3.切換到上級目錄&#xff1a;cd ..&#xff0c;中間有空格 4.列出某目錄所有文件&#xff0c;相當于DOS下的dir&#xff1a;ls c:\\windows 5.查看配置信息&#xff1a;git config --list 以下是顯示信息 …

C#:invoke 與 BeginInvoke使用區別

invoke和begininvoke 區別 一直對invoke和begininvoke的使用和概念比較混亂&#xff0c;這兩天看了些資料&#xff0c;對這兩個的用法和原理有了些新的認識和理解。 首先說下&#xff0c;invoke和begininvoke的使用有兩種情況&#xff1a; 1. control中的invoke、begininvoke。…

Django基本命令

Django基本命令 1.創建一個Django 項目 django_admin.py startproject mysite當前目錄下會生成mysite的工程&#xff0c;目錄結構如下&#xff1a; manage.py ----- Django項目里面的工具&#xff0c;通過它可以調用django shell和數據庫等。settings.py ---- 包含了項目的默認…

Git忽略規則.gitignore梳理

對于經常使用Git的朋友來說&#xff0c;.gitignore配置一定不會陌生。廢話不說多了&#xff0c;接下來就來說說這個.gitignore的使用。首先要強調一點&#xff0c;這個文件的完整文件名就是".gitignore"&#xff0c;注意最前面有個“.”。 一般來說每個Git項目中都需…

第二周CoreIDRAW課總結

1.這節課學到了什么知識&#xff1f; 學到了圖像的復制&#xff0c;再制鼠標復制&#xff0c;重復&#xff0c;還有對象的對齊&#xff0c;對象的分布順序。 2.有哪些心得體會&#xff1f; 做了課本的練習&#xff0c;會用窗口里面的泊塢窗造型命令也作出了一個作品。 3.這節課…

axios關于針對請求時長策略設計的思考

前言 在我們的業務請求中&#xff0c;有很多時候會針對有不同時長的需求策略性設置。這里針對這個需求進行詳細的展開。 針對這種情況&#xff0c;我們的timout的一般是根據請求地址來的&#xff0c;所以核心處理技巧便是如何根據不同的request地址去設置不同的timeout. 我們之…

C#:WinForm無邊框窗體移動方法、模仿鼠標單擊標題欄移動窗體位置

方法一&#xff1a;直接通過修改窗體位置從而達到移動窗體的效果 方法二&#xff1a;直接偽裝發送單擊任務欄消息&#xff0c;讓應用程序誤以為單擊任務欄從而移動窗體 方法一 1.定義一個位置信息Point用于存儲鼠標位置 private Point mPoint; 2.給窗體等控件增加MouseDown…

Python 字典刪除元素clear、pop、popitem

同其它python內建數據類型一樣&#xff0c;字典dict也是有一些實用的操作方法。這里我們要說的是字典刪除方法&#xff1a;clear()、pop()和popitem()&#xff0c;這三種方法的作用不同&#xff0c;操作方法及返回值都不相同。接下來就來查看下這些字典特定方法的具體用法是什么…

reactor模式:多線程的reactor模式

上文說到單線程的reactor模式 reactor模式&#xff1a;單線程的reactor模式 單線程的reactor模式并沒有解決IO和CPU處理速度不匹配問題&#xff0c;所以多線程的reactor模式引入線程池的概念&#xff0c;把耗時的IO操作交由線程池處理&#xff0c;處理完了之后再同步到selecti…

Elasticsearch實戰篇——Spring Boot整合ElasticSearch

2019獨角獸企業重金招聘Python工程師標準>>> 當前Spring Boot很是流行&#xff0c;包括我自己&#xff0c;也是在用Spring Boot集成其他框架進行項目開發&#xff0c;所以這一節&#xff0c;我們一起來探討Spring Boot整合ElasticSearch的問題。 本文主要講以下內容…

C#:Dockpanel的一些入門的基本操作

原文鏈接&#xff1a; 一、引用&#xff1a; 1.建立一個WinForm工程&#xff0c;默認生成了一個WinForm窗體Form1&#xff08;此處默認為主窗體&#xff09;。 2.引用—>添加引用—>瀏覽—>weiFenLuo.winFormsUI.Docking.dll。 3.設置Form1窗體屬性IsMdiContainer…

MyBatis中if,where,set標簽

<if>標簽 <select id"findActiveBlogWithTitleLike"resultType"Blog">SELECT * FROM BLOG WHERE state ‘ACTIVE’ <if test"title ! null">AND title like #{title}</if> </select> if標簽通常伴隨著where,set…

Python3基礎 __repr__ 類的實例對象的名字 可以打印文字(1)

引用自&#xff1a;http://www.bubuko.com/infodetail-1918622.html 這個__repr__的作用從下邊的例子中可以看出,返回實例化對象的表達 code: class MyClass() :def __str__(self) :return "我是MyClass的一個實例"def __repr__(self) :return "這回連print都省…

Day03:文件打開;錯誤處理

錯誤處理 try: #要執行的代碼 except 錯誤的類型&#xff08;可選&#xff09;: #發生錯誤時執行的代碼 finally: #有沒有發生錯誤都執行的代碼 復制代碼with open() as 變量名&#xff1a; with提供一種叫上下文管理協議的python技術&#xff0c;系統會自動關閉文件 open() 默…

Python: pip升級報錯了:You are using pip version 10.0.1, however version 20.3.3 is available.

1,Python使用命令&#xff1a;python -m pip install --upgrade pip升級pip的時候報了下面這個錯 2,換了個命令&#xff1a; python -m pip install --upgrade pip -i https://pypi.douban.com/simple 更新成功了&#xff0c;但又報了一個新的錯誤&#xff1a; AttributeError:…