成都网站开发技术珠海网站开发定制
news/
2025/9/27 12:26:21/
文章来源:
成都网站开发技术,珠海网站开发定制,城乡住房建设厅网站,利用国外网站文章图片做书营利前言 目前网页的主流登录方式是通过手机扫码二维码登录。我看了网上很多关于扫码登录博客后#xff0c;发现基本思路大致是#xff1a;打开网页#xff0c;生成uuid#xff0c;然后长连接请求后端并等待登录认证相应结果#xff0c;而后端每个几百毫秒会循环查询数据库或r…前言 目前网页的主流登录方式是通过手机扫码二维码登录。我看了网上很多关于扫码登录博客后发现基本思路大致是打开网页生成uuid然后长连接请求后端并等待登录认证相应结果而后端每个几百毫秒会循环查询数据库或redis当查询到登录信息后则响应长连接的请求。 然而如果是小型应用则没问题如果用户量并发大则会出现非常严重的性能瓶颈。而问题的关键是使用了循环查询数据库或redis的方案。假设要优化这个方案可以使用java多线程的同步集合CountDownLatch来解决。 一、环境 1.java 8(jdk1.8) 2.maven 3.3.9 3.spring boot 2.0 二、知识点 1.同步集合使用 2.CountDownLatch使用 3.http ajax 4.zxing二维码生成 三、流程及实现原理 1.打开网页通过ajax请求获取二维码图片地址 2.页面渲染二维码图片并通过长连接请求获取后端的登录认证信息 3.事先登录过APP的手机扫码二维码然后APP请求服务器端的API接口把用户认证信息传递到服务器中。 4.后端收到APP的请求后唤醒长连接的等待线程并把用户认证信息写入session。 5.页面得到长连接的响应并跳转到首页。 整个流程图下图所示 四、代码编写 pom.xml文件如下 ?xml version1.0 encodingUTF-8?
project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsdmodelVersion4.0.0/modelVersiongroupIdcom.demo/groupIdartifactIdauth/artifactIdversion0.0.1-SNAPSHOT/versionpackagingjar/packagingnameauth/namedescription二维码登录/descriptionparentgroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-parent/artifactIdversion2.0.0.RELEASE/versionrelativePath / !-- lookup parent from repository --/parentpropertiesproject.build.sourceEncodingUTF-8/project.build.sourceEncodingproject.reporting.outputEncodingUTF-8/project.reporting.outputEncodingjava.version1.8/java.version/propertiesdependenciesdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-thymeleaf/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-devtools/artifactIdscoperuntime/scope/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-test/artifactIdscopetest/scope/dependency!-- zxing --dependencygroupIdcom.google.zxing/groupIdartifactIdcore/artifactIdversion3.3.0/version/dependencydependencygroupIdcom.google.zxing/groupIdartifactIdjavase/artifactIdversion3.3.0/version/dependencydependencygroupIdcommons-codec/groupIdartifactIdcommons-codec/artifactId/dependency/dependenciesbuildpluginsplugingroupIdorg.springframework.boot/groupIdartifactIdspring-boot-maven-plugin/artifactId/plugin/plugins/build/project pom.xml 首先参照《玩转spring boot——简单登录认证》完成简单登录认证。在浏览器中输入http://localhost:8080页面时由于未登录认证则重定向到http://localhost:8080/login页面 代码如下 package com.demo.auth;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;/*** 登录配置 博客出处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;}}
} 其次新建控制器类MainController /*** 控制器* * author 刘冬博客http://www.cnblogs.com/GoodHelper**/
Controller
public class MainController {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;}
} 新建两个html页面index.html和login.html !DOCTYPE html
html xmlns:thhttp://www.thymeleaf.org
head
meta charsetUTF-8
title二维码登录/title
/head
bodyh1二维码登录/h1h4a target_blank hrefhttp://www.cnblogs.com/GoodHelper/from刘冬的博客/a/h4h3 th:text登录用户 ${user}/h3
/body
/html !DOCTYPE html
html xmlns:thhttp://www.thymeleaf.org
head
meta charsetUTF-8
title二维码登录/title
script src//cdn.bootcss.com/angular.js/1.5.6/angular.min.js/script
script typetext/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 || !data.success) {setTimeout($scope.getQrCode(), 1000);return;}//登录成功进去首页location.href /}).error(function(data, status) {console.log(data)console.log(status)//一秒后重新获取登录二维码setTimeout($scope.getQrCode(), 1000);})}$scope.getQrCode();});/*]]*/
/script
/head
body ng-appapp ng-controllerMainControllerh1扫码登录/h1h4a target_blank hrefhttp://www.cnblogs.com/GoodHelper/from刘冬的博客/a/h4img ng-showsrc ng-src{{src}} /
/body
/html login.html页面先请求后端服务器获取登录uuid然后获取到服务器的二维码后在页面渲染二维码。接着使用长连接请求并等待服务器的相应。 然后新建一个承载登录信息的类LoginResponse package com.demo.auth;import java.util.concurrent.CountDownLatch;/*** 登录信息承载类* * author 刘冬博客http://www.cnblogs.com/GoodHelper**/
public class LoginResponse {public CountDownLatch latch;public String user;// 省略 get set
} 最后修改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.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;import javax.imageio.ImageIO;
import javax.servlet.http.HttpSession;import org.apache.commons.codec.binary.Base64;
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 MapString, LoginResponse loginMap new ConcurrentHashMap();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 MapString, Object getQrCode() throws Exception {MapString, Object result new HashMap();result.put(loginId, UUID.randomUUID());// app端登录地址String loginUrl http://localhost:8080/login/setUser/loginId/;result.put(loginUrl, loginUrl);result.put(image, createQrCode(loginUrl));return result;}/*** app二维码登录地址这里为了测试才传{user},实际项目中user是通过其他方式传值* * param loginId* param user* return*/GetMapping(login/setUser/{loginId}/{user})public ResponseBody MapString, Object setUser(PathVariable String loginId, PathVariable String user) {if (loginMap.containsKey(loginId)) {LoginResponse loginResponse loginMap.get(loginId);// 赋值登录用户loginResponse.user user;// 唤醒登录等待线程loginResponse.latch.countDown();}MapString, 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 MapString, Object getResponse(PathVariable String loginId, HttpSession session) {MapString, Object result new HashMap();result.put(loginId, loginId);try {LoginResponse loginResponse null;if (!loginMap.containsKey(loginId)) {loginResponse new LoginResponse();loginMap.put(loginId, loginResponse);} elseloginResponse loginMap.get(loginId);// 第一次判断// 判断是否登录,如果已登录则写入sessionif (loginResponse.user ! null) {session.setAttribute(WebSecurityConfig.SESSION_KEY, loginResponse.user);result.put(success, true);return result;}if (loginResponse.latch null) {loginResponse.latch new CountDownLatch(1);}try {// 线程等待loginResponse.latch.await(5, TimeUnit.MINUTES);} catch (Exception e) {e.printStackTrace();}// 再次判断// 判断是否登录,如果已登录则写入sessionif (loginResponse.user ! null) {session.setAttribute(WebSecurityConfig.SESSION_KEY, loginResponse.user);result.put(success, true);return result;}result.put(success, false);return result;} finally {// 移除登录请求if (loginMap.containsKey(loginId))loginMap.remove(loginId);}}/*** 生成base64二维码* * param content* return* throws Exception*/private String createQrCode(String content) throws Exception {try (ByteArrayOutputStream out new ByteArrayOutputStream()) {HashtableEncodeHintType, Object hints new HashtableEncodeHintType, 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());}}} 其中使用 MapString, LoginResponse loginMap类存储登录请求信息 createQrCode方法是用于生成二维码 getQrCode方法是给页面返回登录uuid和二维码前端页面拿到登录uuid后请求长连接等待二维码的扫码登录结果。 setUser方法是提供给APP端调用的在此过程中通过uuid找到对应的CountDownLatch并唤醒长连接的线程。而这里是为了做演示才把这个方法放到这个类里在实际项目中此方法不一定在这个类里或未必在同一个后端中。另外我把用户信息的传递也写在这个方法中了而实际项目是通过其他的方式来传递用户信息这里仅仅是为了演示方便。 getResponse方法是处理ajax的长连接并使用CountDownLatch等待APP端来唤醒这个线程然后把用户信息写入session。 入口类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);}
} 项目结构如下图所示 五、总结 打开浏览器输入http://localhost:8080。运行效果如下图所以 使用CountDownLatch则避免了每隔500毫秒读一次数据库或redis的频繁查询性能问题。因为操作的是内存数据所以性能非常高。 而CountDownLatch是java多线程中非常实用的类二维码扫码登录就是一个具有代表意义的应用场景。当然如果你不嫌代码量大也可以用waitnotify来实现。另在java.util.concurrent包下也有很多的多线程类能到达同样的目的我这里就不一一例举了。 根据园友的建议我发现本篇文章里的线程阻塞是设计缺陷所以不循环查询数据库或redis里但一台服务器的线程数是有限的。在下篇我会改进这个设计 代码下载 如果你觉得我的博客对你有帮助可以给我点儿打赏左侧微信右侧支付宝。 有可能就是你的一点打赏会让我的博客写的更好:) 返回玩转spring boot系列目录 作者刘冬.NET 博客地址http://www.cnblogs.com/GoodHelper/ 欢迎转载但须保留版权 转载于:https://www.cnblogs.com/GoodHelper/p/8641905.html
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/916608.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!