Spring Boot + Web Socket 实现扫码登录,这种方式太香了!!

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

最近单位又有一个新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了。。赶紧贴代码吧。。


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


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


五、疯狂贴代码

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

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

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


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


这里使用了xmlhttp进行处理。


为什么?


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


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


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


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


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


js

$(document).ready(function(){
    initQrImg();
});
 function initQrImg(){
    $("#qrImgDiv").empty();
    var xmlhttp;
    xmlhttp=new XMLHttpRequest();
    xmlhttp.open("GET",getQrPath,true);
    xmlhttp.responseType = "blob";
    xmlhttp.onload = function(){
        console.log(this);
        uuid = this.getResponseHeader("uuid");
        if (this.status == 200) {
            var blob = this.response;
            var img = document.createElement("img");
            img.className = 'qrCodeBox-img';
            img.onload = function(e) {
                window.URL.revokeObjectURL(img.src);
            };
            img.src = window.URL.createObjectURL(blob);
            document.getElementById("qrImgDiv").appendChild(img);
            initWebSocket();
        }
    }
    xmlhttp.send();
}
var path = "://localhost:8085";
var getQrPath =  "http" + path + "/user/getLoginQr";
var wsPath =     "ws" + path + "/websocket/";
function initWebSocket(){
   if(typeof(WebSocket) == "undefined") {
       console.log("您的浏览器不支持WebSocket");
   }else{
       console.log("您的浏览器支持WebSocket");
       //实现化WebSocket对象,指定要连接的服务器地址与端口  建立连接
       //等同于socket = new WebSocket("ws://localhost:8083/checkcentersys/websocket/20");
       var wsPathStr = wsPath+uuid;
       socket = new WebSocket(wsPathStr);
       //打开事件
       socket.onopen = function() {
           console.log("Socket 已打开");
           //socket.send("这是来自客户端的消息" + location.href + new Date());
       };
       //获得消息事件
       socket.onmessage = function(msg) {
           console.log(msg.data);
           var data = JSON.parse(msg.data);
           if(data.code == 200){
               alert("登录成功!");
               //这里存放自己业务需要的数据。怎么放自己看
               window.sessionStorage.uuid = uuid;
               window.sessionStorage.userId = data.userId;
               window.sessionStorage.projId = data.projId;
               window.location.href = "pages/upload.html"
           }else{
               //如果过期了,关闭连接、重置连接、刷新二维码
               socket.close();
               initQrImg();
           }
           //发现消息进入    开始处理前端触发逻辑
       };
       //关闭事件
       socket.onclose = function() {
           console.log("Socket已关闭");
       };
       //发生了错误事件
       socket.onerror = function() {
           alert("Socket发生了错误");
           //此时可以尝试刷新页面
       }
   }
}

好了。上面已经提到了前端如何配置webSocket。


下面说一下


springBoot中如何操作webSocket

Spring Boot 就不介绍了,基础教程和示例源码看这里:https://github.com/javastacks/spring-boot-best-practice


1、增加pom.xml

image.png

3、定义WebSocketServer

package com.stylefeng.guns.rest.modular.inve.websocket;
/**
 * Created by jiangjiacheng on 2019/6/4.
 */
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import org.springframework.stereotype.Component;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
@ServerEndpoint("/websocket/{sid}")
@Component
public class WebSocketServer {
    static Log log=LogFactory.get(WebSocketServer.class);
    //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
    private static int onlineCount = 0;
    //concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
    private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<WebSocketServer>();
    //与某个客户端的连接会话,需要通过它来给客户端发送数据
    private Session session;
    //接收sid
    private String sid="";
    /**
     * 连接建立成功调用的方法*/
    @OnOpen
    public void onOpen(Session session,@PathParam("sid") String sid) {
        this.session = session;
        webSocketSet.add(this);     //加入set中
        addOnlineCount();           //在线数加1
        log.info("有新窗口开始监听:"+sid+",当前在线人数为" + getOnlineCount());
        this.sid=sid;
        /*try {
            sendMessage("连接成功");
        } catch (IOException e) {
            log.error("websocket IO异常");
        }*/
    }
    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        webSocketSet.remove(this);  //从set中删除
        subOnlineCount();           //在线数减1
        log.info("有一连接关闭!当前在线人数为" + getOnlineCount());
    }
    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息*/
    @OnMessage
    public void onMessage(String message, Session session) {
        log.info("收到来自窗口"+sid+"的信息:"+message);
        //群发消息
        for (WebSocketServer item : webSocketSet) {
            try {
                item.sendMessage(message);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    /**
     *
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.error("发生错误");
        error.printStackTrace();
    }
    /**
     * 实现服务器主动推送
     */
    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
    }
    /**
     * 群发自定义消息
     * */
    public static void sendInfo(String message,@PathParam("sid") String sid) throws IOException {
        log.info("推送消息到窗口"+sid+",推送内容:"+message);
        for (WebSocketServer item : webSocketSet) {
            try {
                //这里可以设定只推送给这个sid的,为null则全部推送
                if(sid == null) {
                    item.sendMessage(message);
                }else if(item.sid.equals(sid)){
                    item.sendMessage(message);
                }
            } catch (IOException e) {
                continue;
            }
        }
    }
    public static synchronized int getOnlineCount() {
        return onlineCount;
    }
    public static synchronized void addOnlineCount() {
        WebSocketServer.onlineCount++;
    }
    public static synchronized void subOnlineCount() {
        WebSocketServer.onlineCount--;
    }
}

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


那么回到刚才的步骤。


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


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


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


贴扫码成功接口


Controller代码:

/**
 * 确认身份接口:确定身份以及判断是否二维码过期等
 * @param token
 * @param userId
 * @return
 */
@RequestMapping(value = "/bindUserIdAndToken" ,method = RequestMethod.GET)
@ResponseBody
public Object bindUserIdAndToken(@RequestParam("token") String token ,
                                 @RequestParam("userId") Integer userId,
                                 @RequestParam(required = false,value = "projId") Integer projId){
    try {
        return new SuccessTip(userService.bindUserIdAndToken(userId,token,projId));
    } catch (Exception e) {
        e.printStackTrace();
        return new ErrorTip(500,e.getMessage());
    }
}

Service代码

@Override
public String bindUserIdAndToken(Integer userId, String token,Integer projId) throws Exception {
    QrLoginToken qrLoginToken = new QrLoginToken();
    qrLoginToken.setToken(token);
    qrLoginToken = qrLoginTokenMapper.selectOne(qrLoginToken);
    if(null == qrLoginToken){
        throw  new Exception("错误的请求!");
    }
    Date createDate = new Date(qrLoginToken.getCreateTime().getTime() + (1000 * 60 * Constant.LOGIN_VALIDATION_TIME));
    Date nowDate = new Date();
    if(nowDate.getTime() > createDate.getTime()){//当前时间大于校验时间
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("code",500);
        jsonObject.put("msg","二维码失效!");
        WebSocketServer.sendInfo(jsonObject.toJSONString(),token);
        throw  new Exception("二维码失效!");
    }
    qrLoginToken.setLoginTime(new Date());
    qrLoginToken.setUserId(userId);
    int i = qrLoginTokenMapper.updateById(qrLoginToken);
    JSONObject jsonObject = new JSONObject();
    jsonObject.put("code",200);
    jsonObject.put("msg","ok");
    jsonObject.put("userId",userId);
    if(ToolUtil.isNotEmpty(projId)){
        jsonObject.put("projId",projId);
    }
    WebSocketServer.sendInfo(jsonObject.toJSONString(),token);
    if(i > 0 ){
        return null;
    }else{
        throw  new Exception("服务器异常!");
    }
}

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


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


//这句话比较关键

WebSocketServer.sendInfo(jsonObject.toJSONString(),token);

1

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


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


原文链接:https://blog.csdn.net/q826qq1878/article/details/91041679


版权声明:本文为CSDN博主「93年颈椎病人」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。


近期热文推荐:

目录
相关文章
|
27天前
|
缓存 安全 Java
《深入理解Spring》过滤器(Filter)——Web请求的第一道防线
Servlet过滤器是Java Web核心组件,可在请求进入容器时进行预处理与响应后处理,适用于日志、认证、安全、跨域等全局性功能,具有比Spring拦截器更早的执行时机和更广的覆盖范围。
|
6月前
|
Java
SpringBoot快速搭建WebSocket服务端和客户端
由于工作需要,研究了SpringBoot搭建WebSocket双向通信的过程,其他的教程看了许多,感觉讲得太复杂,很容易弄乱,这里我只展示快速搭建过程。
1881 1
|
开发框架 前端开发 网络协议
Spring Boot结合Netty和WebSocket,实现后台向前端实时推送信息
【10月更文挑战第18天】 在现代互联网应用中,实时通信变得越来越重要。WebSocket作为一种在单个TCP连接上进行全双工通信的协议,为客户端和服务器之间的实时数据传输提供了一种高效的解决方案。Netty作为一个高性能、事件驱动的NIO框架,它基于Java NIO实现了异步和事件驱动的网络应用程序。Spring Boot是一个基于Spring框架的微服务开发框架,它提供了许多开箱即用的功能和简化配置的机制。本文将详细介绍如何使用Spring Boot集成Netty和WebSocket,实现后台向前端推送信息的功能。
2964 1
|
Java API 数据库
构建RESTful API已经成为现代Web开发的标准做法之一。Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐。
【10月更文挑战第11天】本文介绍如何使用Spring Boot构建在线图书管理系统的RESTful API。通过创建Spring Boot项目,定义`Book`实体类、`BookRepository`接口和`BookService`服务类,最后实现`BookController`控制器来处理HTTP请求,展示了从基础环境搭建到API测试的完整过程。
186 4
|
10月前
|
XML Java 应用服务中间件
Spring Boot 两种部署到服务器的方式
本文介绍了Spring Boot项目的两种部署方式:jar包和war包。Jar包方式使用内置Tomcat,只需配置JDK 1.8及以上环境,通过`nohup java -jar`命令后台运行,并开放服务器端口即可访问。War包则需将项目打包后放入外部Tomcat的webapps目录,修改启动类继承`SpringBootServletInitializer`并调整pom.xml中的打包类型为war,最后启动Tomcat访问应用。两者各有优劣,jar包更简单便捷,而war包适合传统部署场景。需要注意的是,war包部署时,内置Tomcat的端口配置不会生效。
2438 17
Spring Boot 两种部署到服务器的方式
|
9月前
|
消息中间件 XML 前端开发
springBoot集成websocket实时消息推送
本文介绍了如何在Spring Boot项目中集成WebSocket实现实时消息推送。首先,通过引入`spring-boot-starter-websocket`依赖,配置`WebSocketConfig`类来启用WebSocket支持。接着,创建`WebSocketTest`服务器类,处理连接、消息收发及错误等事件,并使用`ConcurrentHashMap`管理用户连接。最后,前端通过JavaScript建立WebSocket连接,监听消息并进行相应处理。此方案适用于需要实时通信的应用场景,如聊天室、通知系统等。
1256 2
|
8月前
|
Java 数据库 微服务
微服务——SpringBoot使用归纳——Spring Boot中的项目属性配置——指定项目配置文件
在实际项目中,开发环境和生产环境的配置往往不同。为简化配置切换,可通过创建 `application-dev.yml` 和 `application-pro.yml` 分别管理开发与生产环境配置,如设置不同端口(8001/8002)。在 `application.yml` 中使用 `spring.profiles.active` 指定加载的配置文件,实现环境快速切换。本节还介绍了通过配置类读取参数的方法,适用于微服务场景,提升代码可维护性。课程源码可从 [Gitee](https://gitee.com/eson15/springboot_study) 下载。
321 0
|
11月前
|
Java 开发者 微服务
Spring Boot 入门:简化 Java Web 开发的强大工具
Spring Boot 是一个开源的 Java 基础框架,用于创建独立、生产级别的基于Spring框架的应用程序。它旨在简化Spring应用的初始搭建以及开发过程。
516 7
Spring Boot 入门:简化 Java Web 开发的强大工具
|
12月前
|
存储 运维 安全
Spring运维之boot项目多环境(yaml 多文件 proerties)及分组管理与开发控制
通过以上措施,可以保证Spring Boot项目的配置管理在专业水准上,并且易于维护和管理,符合搜索引擎收录标准。
673 2
|
前端开发 Java C++
RSocket vs WebSocket:Spring Boot 3.3 中的两大实时通信利器
本文介绍了在 Spring Boot 3.3 中使用 RSocket 和 WebSocket 实现实时通信的方法。RSocket 是一种高效的网络通信协议,支持多种通信模式,适用于微服务和流式数据传输。WebSocket 则是一种标准协议,支持全双工通信,适合实时数据更新场景。文章通过一个完整的示例,展示了如何配置项目、实现前后端交互和消息传递,并提供了详细的代码示例。通过这些技术,可以大幅提升系统的响应速度和处理效率。