客户端通过二维码扫码登陆web

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xingfuzhijianxia/article/details/78355483

分两部分,第一部分生成二维码,第二部分长连接轮询方式实现客户端扫码。
一、 前端
使用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)、弹幕、多玩家玩游戏、协同编辑、股票基金实时报价、体育实况更新、视频会议/聊天、基于位置的应用、在线教育、智能家居等高实时性的场景。

以上

猜你喜欢

转载自blog.csdn.net/xingfuzhijianxia/article/details/78355483