SpringBoot+webSocket 实现扫码登录功能

举报
民工哥 发表于 2022/04/14 02:48:14 2022/04/14
【摘要】 最近单位又有一个新Java项目。 涉及到扫码登录。之前项目使用的是 ajax轮询的方式。感觉太low了。 所以这次用webSocket的方式进行实现 好。废话不多说!咱们开始!! 一、首先咱们需要一张表 这表是干啥的呢? 就是记录一下谁扫码了。谁登录了。 User_Token表 字段如下: uuid : ...

最近单位又有一个新Java项目。

涉及到扫码登录。之前项目使用的是 ajax轮询的方式。感觉太low了。

所以这次用webSocket的方式进行实现

好。废话不多说!咱们开始!!

一、首先咱们需要一张表

这表是干啥的呢? 就是记录一下谁扫码了。谁登录了。

User_Token表

字段如下:

  • uuid : 用于确保唯一性

  • userId : 谁登录的

  • loginTime : 登录时间

  • createTime :创建时间 用于判断是否过期

  • state: 是否二维码失效  0有效 1失效

二、角色都有哪些

咱们还需要分析一下子。扫码登录这个业务逻辑都有哪些角色

  • android端 or 微信Web端 : 扫码

  • PC端 : 被扫。登录

  • 服务端: 掌控全局,提供接口。

三、接口都需要哪些?

有了角色。你用大腿也能想出来接口了对不对!!

所以咱们的接口有2个!

  • 生成二维码接口:生成一个二维码。二维码中有UUID。

  • 确认身份接口:确定身份以及判断是否二维码过期等

四、步骤

那句话怎么说的来着。要把大象装冰箱一共分几步?

  • PC端打开。 调用生成二维码接口 并与 服务端建立链接。链接使用uuid进行绑定

  • 微信Web端进行扫码。获取二维码中的uuid。

  • 微信Web端拿到uuid以后。显示是否登录页面。点击确定后 调用 确认身份接口。

  • 确认身份接口通过以后。 服务端给PC端发送信息。 完成登录。 此时链接断开。

好了!分析完了这些。你们一定在想。。还有完没完啊。。不要在BB了。。赶紧贴代码吧。。

作者:观众老爷们。我这是在教给你们如何思考的方法呀?

那么开始贴代码吧!希望大家在看到的同时也可以自己进行思考。

五、疯狂贴代码

首先需要获取二维码的代码对不对! 贴!


   
  1. //获取登录二维码、放入Token
  2.     @RequestMapping(value = "/getLoginQr" ,method = RequestMethod.GET)
  3.     public void createCodeImg(HttpServletRequest request, HttpServletResponse response){
  4.         response.setHeader("Pragma""No-cache");
  5.         response.setHeader("Cache-Control""no-cache");
  6.  
  7.         response.setDateHeader("Expires"0);
  8.         response.setContentType("image/jpeg");
  9.  
  10.         try {
  11.             //这里没啥操作 就是生成一个UUID插入 数据库的表里
  12.             String uuid = userService.createQrImg();
  13.             response.setHeader("uuid", uuid);
  14.             // 这里是开源工具类 hutool里的QrCodeUtil 
  15.             // 网址:http://hutool.mydoc.io/
  16.             QrCodeUtil.generate(uuid, 300300"jpg",response.getOutputStream());
  17.         } catch (Exception e) {
  18.             e.printStackTrace();
  19.         }
  20.     }

有了获取二维码的接口。相对的前端需要调用。

知识点:动态加载图片流并取出header中的参数

这里使用了xmlhttp进行处理。

为什么?

因为后端返回的是一个流。

那么流中。就是放置了二维码中的uuid。 这个uuid作为一次会话的标识符使用。

那么前端也需要拿到。 跟后端进行webSocket链接。

这样有人扫码后。 服务端才可以使用webSocket的方式通知前端。有人扫码成功了。你做你的业务吧。酱紫。

所以为了拿到请求中 header中放置的uuid 所以这样通过xmlhttp进行处理


   
  1. html
  2.  <div class="qrCodeImg-box" id="qrImgDiv"></div>

js


   
  1. $(document).ready(function(){
  2.     initQrImg();
  3. });
  4.  
  5.  
  6.  function initQrImg(){
  7.             $("#qrImgDiv").empty();
  8.  
  9.             var xmlhttp;
  10.             xmlhttp=new XMLHttpRequest();
  11.             xmlhttp.open("GET",getQrPath,true);
  12.             xmlhttp.responseType = "blob";
  13.             xmlhttp.onload = function(){
  14.                 console.log(this);
  15.                 uuid = this.getResponseHeader("uuid");
  16.  
  17.                 if (this.status == 200) {
  18.                     var blob = this.response;
  19.                     var img = document.createElement("img");
  20.                     img.className = 'qrCodeBox-img';
  21.                     img.onload = function(e) {
  22.                         window.URL.revokeObjectURL(img.src);
  23.                     };
  24.                     img.src = window.URL.createObjectURL(blob);
  25.                     document.getElementById("qrImgDiv").appendChild(img);
  26.  
  27.                     initWebSocket();
  28.                 }
  29.             }
  30.             xmlhttp.send();
  31.         }
  32.  
  33.  
  34.  
  35.        var path = "://localhost:8085";
  36.        var getQrPath =  "http" + path + "/user/getLoginQr";
  37.        var wsPath =     "ws" + path + "/websocket/";
  38.  
  39.  
  40.  
  41.        function initWebSocket(){
  42.  
  43.            if(typeof(WebSocket) == "undefined") {
  44.                console.log("您的浏览器不支持WebSocket");
  45.            }else{
  46.                console.log("您的浏览器支持WebSocket");
  47.                //实现化WebSocket对象,指定要连接的服务器地址与端口  建立连接
  48.                //等同于socket = new WebSocket("ws://localhost:8083/checkcentersys/websocket/20");
  49.                var wsPathStr = wsPath+uuid;
  50.                socket = new WebSocket(wsPathStr);
  51.                //打开事件
  52.                socket.onopen = function() {
  53.                    console.log("Socket 已打开");
  54.                    //socket.send("这是来自客户端的消息" + location.href + new Date());
  55.                };
  56.                //获得消息事件
  57.                socket.onmessage = function(msg) {
  58.                    console.log(msg.data);
  59.                    var data = JSON.parse(msg.data);
  60.                    if(data.code == 200){
  61.                        alert("登录成功!");
  62.                        //这里存放自己业务需要的数据。怎么放自己看
  63.                        window.sessionStorage.uuid = uuid;
  64.                        window.sessionStorage.userId = data.userId;
  65.                        window.sessionStorage.projId = data.projId;
  66.  
  67.                        window.location.href = "pages/upload.html"
  68.                    }else{
  69.                        //如果过期了,关闭连接、重置连接、刷新二维码
  70.                        socket.close();
  71.                        initQrImg();
  72.                    }
  73.                    //发现消息进入    开始处理前端触发逻辑
  74.                };
  75.                //关闭事件
  76.                socket.onclose = function() {
  77.                    console.log("Socket已关闭");
  78.                };
  79.                //发生了错误事件
  80.                socket.onerror = function() {
  81.                    alert("Socket发生了错误");
  82.                    //此时可以尝试刷新页面
  83.                }
  84.            }
  85.  
  86.        }

好了。上面已经提到了前端如何配置webSocket。欢迎关注公众号Java编程鸭,后台回复“码农大礼包”,送你一份宝典!

下面说一下

springBoot中如何操作webSocket

1、增加pom.xml


   
  1. <dependency>
  2.     <groupId>org.springframework.boot</groupId>
  3.     <artifactId>spring-boot-starter-websocket</artifactId>
  4. </dependency>

2、增加一个Bean


   
  1. /**
  2.  * WebSocket的支持
  3.  * @return
  4.  */
  5. @Bean
  6. public ServerEndpointExporter serverEndpointExporter() {
  7.     return new ServerEndpointExporter();
  8. }

3、定义WebSocketServer


   
  1. package com.stylefeng.guns.rest.modular.inve.websocket;
  2.  
  3. /**
  4.  * Created by jiangjiacheng on 2019/6/4.
  5.  */
  6. import java.io.IOException;
  7. import java.util.concurrent.CopyOnWriteArraySet;
  8.  
  9. import javax.websocket.OnClose;
  10. import javax.websocket.OnError;
  11. import javax.websocket.OnMessage;
  12. import javax.websocket.OnOpen;
  13. import javax.websocket.Session;
  14. import javax.websocket.server.PathParam;
  15. import javax.websocket.server.ServerEndpoint;
  16. import org.springframework.stereotype.Component;
  17. import cn.hutool.log.Log;
  18. import cn.hutool.log.LogFactory;
  19.  
  20. @ServerEndpoint("/websocket/{sid}")
  21. @Component
  22. public class WebSocketServer {
  23.  
  24.     static Log log=LogFactory.get(WebSocketServer.class);
  25.  
  26.     //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
  27.     private static int onlineCount = 0;
  28.  
  29.     //concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
  30.     private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<WebSocketServer>();
  31.  
  32.     //与某个客户端的连接会话,需要通过它来给客户端发送数据
  33.     private Session session;
  34.  
  35.     //接收sid
  36.     private String sid="";
  37.  
  38.     /**
  39.      * 连接建立成功调用的方法*/
  40.     @OnOpen
  41.     public void onOpen(Session session,@PathParam("sid") String sid) {
  42.         this.session = session;
  43.         webSocketSet.add(this);     //加入set中
  44.         addOnlineCount();           //在线数加1
  45.         log.info("有新窗口开始监听:"+sid+",当前在线人数为" + getOnlineCount());
  46.         this.sid=sid;
  47.         /*try {
  48.             sendMessage("连接成功");
  49.         } catch (IOException e) {
  50.             log.error("websocket IO异常");
  51.         }*/
  52.     }
  53.  
  54.     /**
  55.      * 连接关闭调用的方法
  56.      */
  57.     @OnClose
  58.     public void onClose() {
  59.         webSocketSet.remove(this);  //从set中删除
  60.         subOnlineCount();           //在线数减1
  61.         log.info("有一连接关闭!当前在线人数为" + getOnlineCount());
  62.     }
  63.  
  64.     /**
  65.      * 收到客户端消息后调用的方法
  66.      *
  67.      * @param message 客户端发送过来的消息*/
  68.     @OnMessage
  69.     public void onMessage(String message, Session session) {
  70.         log.info("收到来自窗口"+sid+"的信息:"+message);
  71.         //群发消息
  72.         for (WebSocketServer item : webSocketSet) {
  73.             try {
  74.                 item.sendMessage(message);
  75.             } catch (IOException e) {
  76.                 e.printStackTrace();
  77.             }
  78.         }
  79.     }
  80.  
  81.     /**
  82.      *
  83.      * @param session
  84.      * @param error
  85.      */
  86.     @OnError
  87.     public void onError(Session session, Throwable error) {
  88.         log.error("发生错误");
  89.         error.printStackTrace();
  90.     }
  91.     /**
  92.      * 实现服务器主动推送
  93.      */
  94.     public void sendMessage(String message) throws IOException {
  95.         this.session.getBasicRemote().sendText(message);
  96.     }
  97.  
  98.  
  99.     /**
  100.      * 群发自定义消息
  101.      * */
  102.     public static void sendInfo(String message,@PathParam("sid") String sid) throws IOException {
  103.         log.info("推送消息到窗口"+sid+",推送内容:"+message);
  104.         for (WebSocketServer item : webSocketSet) {
  105.             try {
  106.                 //这里可以设定只推送给这个sid的,为null则全部推送
  107.                 if(sid == null) {
  108.                     item.sendMessage(message);
  109.                 }else if(item.sid.equals(sid)){
  110.                     item.sendMessage(message);
  111.                 }
  112.             } catch (IOException e) {
  113.                 continue;
  114.             }
  115.         }
  116.     }
  117.  
  118.     public static synchronized int getOnlineCount() {
  119.         return onlineCount;
  120.     }
  121.  
  122.     public static synchronized void addOnlineCount() {
  123.         WebSocketServer.onlineCount++;
  124.     }
  125.  
  126.     public static synchronized void subOnlineCount() {
  127.         WebSocketServer.onlineCount--;
  128.     }
  129. }

这样就增加了webSocket的支持啦。

那么回到刚才的步骤。

1、首先PC端调用接口展示出来了二维码。

2、请求二维码中的http请求。就有uuid在 header中。直接取到uuid 作为webSocket的标识sid进行连接。

3、然后手机端使用相机拿到二维码中的uuid。 使用uuid + userid 请求 扫码成功接口。

贴扫码成功接口

Controller代码:


   
  1. /**
  2.      * 确认身份接口:确定身份以及判断是否二维码过期等
  3.      * @param token
  4.      * @param userId
  5.      * @return
  6.      */
  7.     @RequestMapping(value = "/bindUserIdAndToken" ,method = RequestMethod.GET)
  8.     @ResponseBody
  9.     public Object bindUserIdAndToken(@RequestParam("token") String token ,
  10.                                      @RequestParam("userId") Integer userId,
  11.                                      @RequestParam(required = false,value = "projId") Integer projId){
  12.  
  13.         try {
  14.             return new SuccessTip(userService.bindUserIdAndToken(userId,token,projId));
  15.         } catch (Exception e) {
  16.             e.printStackTrace();
  17.             return new ErrorTip(500,e.getMessage());
  18.         }
  19.  
  20.     }

Service代码


   
  1. @Override
  2.     public String bindUserIdAndToken(Integer userId, String token,Integer projId) throws Exception {
  3.  
  4.         QrLoginToken qrLoginToken = new QrLoginToken();
  5.         qrLoginToken.setToken(token);
  6.         qrLoginToken = qrLoginTokenMapper.selectOne(qrLoginToken);
  7.  
  8.         if(null == qrLoginToken){
  9.             throw  new Exception("错误的请求!");
  10.         }
  11.  
  12.         Date createDate = new Date(qrLoginToken.getCreateTime().getTime() + (1000 * 60 * Constant.LOGIN_VALIDATION_TIME));
  13.         Date nowDate = new Date();
  14.         if(nowDate.getTime() > createDate.getTime()){//当前时间大于校验时间
  15.  
  16.             JSONObject jsonObject = new JSONObject();
  17.             jsonObject.put("code",500);
  18.             jsonObject.put("msg","二维码失效!");
  19.             WebSocketServer.sendInfo(jsonObject.toJSONString(),token);
  20.  
  21.             throw  new Exception("二维码失效!");
  22.         }
  23.  
  24.         qrLoginToken.setLoginTime(new Date());
  25.         qrLoginToken.setUserId(userId);
  26.  
  27.         int i = qrLoginTokenMapper.updateById(qrLoginToken);
  28.  
  29.         JSONObject jsonObject = new JSONObject();
  30.         jsonObject.put("code",200);
  31.         jsonObject.put("msg","ok");
  32.         jsonObject.put("userId",userId);
  33.         if(ToolUtil.isNotEmpty(projId)){
  34.             jsonObject.put("projId",projId);
  35.         }
  36.         WebSocketServer.sendInfo(jsonObject.toJSONString(),token);
  37.  
  38.         if(i > 0 ){
  39.             return null;
  40.         }else{
  41.             throw  new Exception("服务器异常!");
  42.         }
  43.     }

逻辑大概就是判断一下 token对不对

如果对的话。 时间是否过期。如果没有过期进行业务逻辑操作


   
  1. //这句话比较关键
  2. WebSocketServer.sendInfo(jsonObject.toJSONString(),token);

就是通知前端 已经登录成功了。 并且给他业务所需要的内容。

然后前端代码接收到了。 就进行业务逻辑操作就可以啦。

(感谢阅读,希望对你所有帮助)

来源:blog.csdn.net/q826qq1878/article/details/91041679

END


   
  1. 看完本文有收获?请转发分享给更多人
  2. 关注「Java编程鸭」,提升Java技能
  3. 关注Java编程鸭微信公众号,后台回复:码农大礼包 可以获取最新整理的技术资料一份。涵盖Java 框架学习、架构师学习等!
  4. 文章有帮助的话,在看,转发吧。
  5. 谢谢支持哟 (*^__^*)

文章来源: mingongge.blog.csdn.net,作者:民工哥,版权归原作者所有,如需转载,请联系作者。

原文链接:mingongge.blog.csdn.net/article/details/122207087

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。