客戶端通過二維碼掃碼登陸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)、彈幕、多玩家玩遊戲、協同編輯、股票基金實時報價、體育實況更新、視訊會議/聊天、基於位置的應用、線上教育、智慧家居等高實時性的場景。
以上