1. 程式人生 > >客戶端通過二維碼掃碼登陸web

客戶端通過二維碼掃碼登陸web

分兩部分,第一部分生成二維碼,第二部分長連線輪詢方式實現客戶端掃碼。
一、 前端
使用jQuery qrcode外掛生成待掃的二維碼,
qrcode其實是通過使用jQuery實現圖形渲染,畫圖,支援canvas(HTML5)和table兩種方式。
實現方式如下

//1.引入qrcode相關js
<script type="text/javascript" src="${ctx}/plugins/jquery/jquery.qrcode.min.js"></script>
<script type="text/javascript" src="${ctx}/plugins/jquery/qrcode.js"
></script> //2.渲染圖形二維碼 jQuery(document).ready(function() { var guuid = genuuid(); $("#qrcode").qrcode({ render: "canvas", // 渲染方式有table方式和canvas方式 width: 256, //預設寬度 height: 256, //預設高度 text:'{"uuid":"'+guuid+'"}', //二維碼內容,此處直接使用生成的uuid,客戶端自行拼接回調地址,呼叫登陸介面
typeNumber: -1, //計算模式一般預設為-1 correctLevel: 2, //二維碼糾錯級別 background: "#ffffff", //背景顏色 foreground: "#000000" //二維碼顏色 }); var margin = ($("#qrcode").height() - $("#qrCodeIco").height()) / 2; //控制Logo圖示的位置 $("#qrCodeIco").css("margin", margin); validateLogin(guuid); }); //ajax
function validateLogin(guuid){ $.get(_ctx_+"/QRLongConnCheckServlet?uuid=" + guuid , function(data, status) { if(data == ""){ validateLogin(guuid); }else{ var user = eval("(" + data + ")"); var url =_ctx_+"/mobile/login.action?userId="+user.userId; savecooike(user.userName); lg(url);//合法登陸系統後,頁面跳轉 } }); } //生成uuid function genuuid() { var s = []; var hexDigits = "0123456789abcdef"; for (var i = 0; i < 36; i++) { s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1); } s[14] = "4"; // bits 12-15 of the time_hi_and_version field to 0010 s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01 s[8] = s[13] = s[18] = s[23] = ""; var uuid = s.join(""); return uuid; } //3.頁面部分 <div class="login_box"> <div class="qrcode"> <div id="qrcode" style="margin-top: 30px"> <img id="qrCodeIco" src="${ctx}/images/default.png" style="position: absolute;width: 30px; height: 30px;" /> //設定二維碼預設LOGO圖片 </div> <div> <p class="sub_title">使用APP客戶端掃碼登入</p> </div> </div> </div>

二、 後臺
實現方式有多種,ajax輪詢,http長連線(comet…),websocket,eventSource。

此次實現選擇了簡單的輪詢方式,使用者成功登陸後要考慮許可權控制部分等。


/**
 * 通過長連線驗證掃碼登陸情況
 * @author ebonyzhang
 */
public class QRLongConnCheckServlet extends HttpServlet {

    private static Logger logger = Logger.getLogger(QRLongConnCheckServlet.class);

    private static final long serialVersionUID = 7823636638598221617L;
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }
    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String uuid = request.getParameter("uuid");
        String jsonStr = "";
        long inTime = new Date().getTime();
        Boolean bool = true;
        while (bool) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            User u = QRLoginUser.getLoginUserMap().get(uuid);
            logger.info("uuid="+uuid+",user="+u);
            if(!EmptyUtils.isEmpty(u)){
                bool = false;
                jsonStr = "{\"userId\":\""+u.getId()+"\",\"userName\":\""+u.getUserName()+"\"}";
                QRLoginUser.getLoginUserMap().remove(uuid);
                logger.info(u.getUserName()+"login ok : " + jsonStr);
            }else{
                if(new Date().getTime() - inTime > 5000){
                    bool = false;
                }
            }
        }
        PrintWriter out = response.getWriter();
        out.print(jsonStr);
        out.flush();
        out.close();
    }
}

//手機端掃碼方法
    /**
     * 手機端掃碼
     * @return
     */
    @Action(value="loginQR")
    public String loginQR(){
        Gson gson = new GsonBuilder().setDateFormat(DATE_JSON_FORMAT).create();
        try {
            User lu = QRLoginUser.getLoginUserMap().get(uuid);
            if(lu == null){
                //掃碼時UserId
                User u = userService.getUserByid(this.userId);
                if(!EmptyUtils.isEmpty(u)){
                    if (u.getEnable().booleanValue()) {
                        QRLoginUser.getLoginUserMap().put(uuid,u);
                        head.setSuccess(Boolean.TRUE);
                        head.setMsg("登陸成功!");
                      } else {
                          head.setMsg("該使用者已被停用,請聯絡管理員!");
                          head.setSuccess(Boolean.FALSE);
                      }
                }else{
                     head.setMsg("您沒有登陸");
                     head.setSuccess(Boolean.FALSE);
                }
            }
        } catch (Exception e) {
             head.setMsg("登陸系統異常,請重新掃碼!");
             head.setSuccess(Boolean.FALSE);
            logger.error("登陸系統異常,異常資訊為:"+e.getMessage());
        }
        setJsonString(gson.toJson(result));
        return SUCCESS;
    }

    //網頁跳轉方法
    /**
     * 網頁跳轉
     * @return
     */
    @Action(value="login")
    public String login(){
         Map<String, Object> json = new HashMap<String, Object>();
        try {
            Map<String, Object> loginMap = userDetailService.forQRLogin(userId);
            if(!EmptyUtils.isEmpty(loginMap)){
                getSessionMap().putAll(loginMap); 
                json.put("success", Boolean.TRUE);
                json.put("user", loginMap.get(UserService.CUR_USER));
            }else{
                json.put("success", Boolean.FALSE);
                json.put("msg","請登陸手機客戶端");
            }
        } catch (Exception e) {
            logger.error("登陸系統異常,異常資訊為:"+e.getMessage());
        }
        Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create();
        setJsonString(gson.toJson(json));
        return SUCCESS;
    } 

三、其他補充
從這個實現得到的思路
輪詢方式:客戶端定時向服務端傳送ajax請求,伺服器接收到請求後馬上返回訊息並關閉連線。
優點:後端程式編寫比較容易。
缺點:TCP的建立和關閉操作浪費時間和頻寬,請求中有大半是無用,浪費頻寬和伺服器資源。
例項:適於小型應用。

長輪詢:客戶端向伺服器傳送Ajax請求,伺服器接到請求後hold住連線,直到有新訊息才返回響應資訊並關閉連線,客戶端處理完響應資訊後再向伺服器傳送新的請求。
優點:在無訊息的情況下不會頻繁的請求,耗費資源小。
缺點:伺服器hold連線會消耗資源,返回資料順序無保證,難於管理維護。
例項:WebQQ、Hi網頁版、Facebook IM。

長連線:在頁面裡嵌入一個隱蔵iframe,將這個隱蔵iframe的src屬性設為對一個長連線的請求或是採用xhr請求,伺服器端就能源源不斷地往客戶端輸入資料。
優點:訊息即時到達,不發無用請求;管理起來也相對方便。
缺點:伺服器維護一個長連線會增加開銷,當客戶端越來越多的時候,server壓力大!
例項:Gmail聊天

Flash Socket:在頁面中內嵌入一個使用了Socket類的 Flash 程式JavaScript通過呼叫此Flash程式提供的Socket介面與伺服器端的Socket介面進行通訊,JavaScript在收到伺服器端傳送的資訊後控制頁面的顯示。
優點:實現真正的即時通訊,而不是偽即時。
缺點:客戶端必須安裝Flash外掛,移動端支援不好,IOS系統中沒有flash的存在;非HTTP協議,無法自動穿越防火牆。
例項:網路互動遊戲。

webSocket:HTML5 WebSocket設計出來的目的就是取代輪詢和長連線,使客戶端瀏覽器具備像C/S框架下桌面系統的即時通訊能力,實現了瀏覽器和伺服器全雙工通訊,建立在TCP之上,
雖然WebSocket和HTTP一樣通過TCP來傳輸資料,但WebSocket可以主動的向對方傳送或接收資料,就像Socket一樣;並且WebSocket需要類似TCP的客戶端和服務端通過握手連線,
連線成功後才能互相通訊。
優點:雙向通訊、事件驅動、非同步、使用ws或wss協議的客戶端能夠真正實現意義上的推送功能。
缺點:少部分瀏覽器不支援。
示例:社交聊天(微信、QQ)、彈幕、多玩家玩遊戲、協同編輯、股票基金實時報價、體育實況更新、視訊會議/聊天、基於位置的應用、線上教育、智慧家居等高實時性的場景。

以上