前言

  目前网页的主流登录方式是通过手机扫码二维码登录。我看了网上很多关于扫码登录博客后,发现基本思路大致是:打开网页,生成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文件如下:

首先,参照《玩转spring boot——简单登录认证》完成简单登录认证。在浏览器中输入http://localhost:8080页面时,由于未登录认证,则重定向到http://localhost:8080/login页面

代码如下:

其次,新建控制器类:MainController

新建两个html页面:index.html和login.html

login.html页面先请求后端服务器,获取登录uuid,然后获取到服务器的二维码后在页面渲染二维码。接着使用长连接请求并等待服务器的相应。

然后新建一个承载登录信息的类:LoginResponse

最后修改MainController类,最终的代码如下:

其中,使用 Map<String, LoginResponse> loginMap类存储登录请求信息

createQrCode方法是用于生成二维码

getQrCode方法是给页面返回登录uuid和二维码,前端页面拿到登录uuid后请求长连接等待二维码的扫码登录结果。

setUser方法是提供给APP端调用的,在此过程中通过uuid找到对应的CountDownLatch,并唤醒长连接的线程。而这里是为了做演示才把这个方法放到这个类里,在实际项目中,此方法不一定在这个类里或未必在同一个后端中。另外我把用户信息的传递也写在这个方法中了,而实际项目是通过其他的方式来传递用户信息,这里仅仅是为了演示方便。

getResponse方法是处理ajax的长连接,并使用CountDownLatch等待APP端来唤醒这个线程,然后把用户信息写入session。

入口类App.java

项目结构如下图所示:

五、总结

打开浏览器输入http://localhost:8080。运行效果如下图所以:

使用CountDownLatch则避免了每隔500毫秒读一次数据库或redis的频繁查询性能问题。因为操作的是内存数据,所以性能非常高。

而CountDownLatch是java多线程中非常实用的类,二维码扫码登录就是一个具有代表意义的应用场景。当然,如果你不嫌代码量大也可以用wait+notify来实现。另在java.util.concurrent包下,也有很多的多线程类能到达同样的目的,我这里就不一一例举了。

根据园友的建议,我发现本篇文章里的线程阻塞是设计缺陷,所以不循环查询数据库或redis里,但一台服务器的线程数是有限的。在下篇我会改进这个设计

源码