SpringBoot 實現掃碼登錄

最近有個項目涉及到 websocket 實現掃碼登錄,看到一篇不錯的技術文,分享一下。

一、首先咱們需要一張表

這表是幹啥的呢?就是記錄一下誰掃碼了。誰登錄了。

User_Token 表

字段如下:

二、角色都有哪些

咱們還需要分析一下子。掃碼登錄這個業務邏輯都有哪些角色

三、接口都需要哪些?

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

所以咱們的接口有 2 個!

四、步驟

那句話怎麼說的來着。要把大象裝冰箱一共分幾步?

好了!分析完了這些。你們一定在想。。還有完沒完啊。。不要在 BB 了。。趕緊貼代碼吧。。微信搜索公衆號:Java 後端編程,回覆:java 領取資料 。

作者:觀衆老爺們。我這是在教給你們如何思考的方法呀?

那麼開始貼代碼吧!希望大家在看到的同時也可以自己進行思考。

五、瘋狂貼代碼

首先需要獲取二維碼的代碼對不對!貼!

//獲取登錄二維碼、放入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 進行處理。

爲什麼?

<div class="qrCodeImg-box" id="qrImgDiv"></div>  
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發生了錯誤");  
           //此時可以嘗試刷新頁面  
       }  
   }  
  
}

Spring Boot 中操作 WebSocket

1、增加 pom.xml

<dependency>  
    <groupId>org.springframework.boot</groupId>  
    <artifactId>spring-boot-starter-websocket</artifactId>  
</dependency>

2、增加一個 Bean

/**  
 \* WebSocket的支持  
 * @return  
 */  
@Bean  
public ServerEndpointExporter serverEndpointExporter() {  
    return new ServerEndpointExporter();  
}

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);

就是通知前端已經登錄成功了。並且給他業務所需要的內容。然後前端代碼接收到了。就進行業務邏輯操作就可以啦。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/oAntjtFldqkVSVtCCTSu2g