java实现简单扫码登录功能

519 阅读5分钟
原文链接: blog.csdn.net

java实现简单扫码登录功能

  • 模仿微信pc网页版扫码登录
  • 使用js代码生成qrcode二维码减轻服务器压力
  • js循环请求服务端,判断是否qrcode被扫
  • 二维码超时失效功能
  • 二维码被扫成功登录,服务端产生sessionId,传到页面使用js保存cookie
  • 多线程

生成qrcode相关js jquery.qrcode.js

-

代码

页面div

<div class="pc_qr_code">
              <input type="hidden" id="uuid" value="${uuid }"/>
</div>
  <div id="result">请使用手机扫码</div>


主要js

//生成二维码
        !function(){
             var uuid = $("#uuid").val();
             var content;
             content = "..........do?uuid="+uuid;
             //console.dir(content);
             $('.pc_qr_code').qrcode({
                 render:"canvas",
                 width:200,
                 height:200,
                 correctLevel:0,
                 text:content,
                 background:"#ffffff",
                 foreground:"black",
                 src:"/logo.png"
                 });  
            setCookie("sid", 123, -1*60*60*1000);
            keepPool();//自动循环调用
        }();

        function keepPool(){
            var uuid = $("#uuid").val();
            $.get(ctx+"/web/login/pool.do",{uuid:uuid,},function(msg){//如果放入一个不存在的网址怎么办?
                //console.log(msg);
                if(msg.successFlag == '1'){
                    $("#result").html("扫码成功");
                    setCookie(msg.data.cname, msg.data.cvalue, 3*60*60*1000);
                    //alert("将跳转...");
                    window.location.href = ctx +"/webstage/login/success.do";
                }else if(msg.successFlag == '0'){
                    $("#result").html("该二维码已经失效,请重新获取");
                }else{
                    keepPool();
                }

            }); 
        }

        //设置cookie
        function setCookie(cname, cvalue, expireTime) {
         var d = new Date();
         d.setTime(d.getTime() + expireTime);//设置过期时间
         var expires = "expires="+d.toUTCString();
         var path = "path=/"
         document.cookie = cname + "=" + cvalue + "; " + expires + "; " + path;
        }

java代码

//二维码首页
public String index() {
        try {
            uuid = UUID.randomUUID().toString();
            super.getRequest().setAttribute("uuid", uuid);
            ScanPool pool = new ScanPool();
            pool.setCreateTime(System.currentTimeMillis());
            Map<String, ScanPool> map = new HashMap<String, ScanPool>(1);
            map.put(uuid, pool);
            PoolCache.cacheMap.put(uuid, pool);
            pool = null;
        } catch (Exception e) {
            Log4jUtil.CommonLog.error("pc生成二维码登录", e);
        }
        return "index";
    }
//判断二维码是否被扫描
public void pool() {
        DataResultInfo result = null;
        System.out.println("检测[   " + uuid + "   ]是否登录");
        ScanPool pool = null; 
        if(MapUtils.isNotEmpty(PoolCache.cacheMap)) pool = PoolCache.cacheMap.get(uuid);

        try {
            if (pool == null) {
                // 扫码超时,进线程休眠
                result = DataResultInfo.getInstance().failure();
                result.setSuccessFlag(CommonConstant.Zero);
                result.setExtension(CommonConstant.Zero, "该二维码已经失效,请重新获取");
                Thread.sleep(10 * 1000L);
            } else {
                // 使用计时器,固定时间后不再等待扫描结果--防止页面访问超时
                new Thread(new ScanCounter(uuid, pool)).start();

                boolean scanFlag = pool.getScanStatus(); //这里得到的ScanPool(时间靠前)和用户使用手机扫码后得到的不是一个,用户扫码后又重新更新了ScanPool对象,并重新放入了redis中,,所以这里要等待上面的计时器走完,才能获得最新的ScanPool
                if (scanFlag) {
                    result = DataResultInfo.getSuccess();
                    // 根据uuid从redis中获取pool对象,得到对应的sessionId,返给页面,通过js存cookie中
                    JSONObject jsonObj = new JSONObject();
                    jsonObj.put("cname", CookieConstant.SESSION_KEY);
                    jsonObj.put("cvalue", pool.getSession());
                    result.setData(jsonObj);
                } else {
                    result = DataResultInfo.getInstance().failure();
                    result.setMessage("等待扫描");
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        sendJsonMessage(result);
    }

//手机扫码接口(以id和token作为用户身份登录)
 public String phoneScanLogin() {
        DataResultInfo result = null;
         ScanPool pool = null; 
         if(MapUtils.isNotEmpty(PoolCache.cacheMap)) pool = PoolCache.cacheMap.get(uuid);

        try {
            if (pool == null) {
                result = DataResultInfo.getInstance().failure();
                result.setMessage("该二维码已经失效,请重新获取");
            } else {
                if (StringUtils.isNotEmpty(id) && StringUtils.isNotEmpty(token)) {
                    //根据id和token查询后台,获取用户信息userBean
                    String redisToken = redisUtil.getRedis(RedisKeyConstant.APP_TOKEN+userId);
                    if(redisToken != null && redisToken.equals(token)){
                   UserBean userBean = userService.findByUserId(Long.valueOf(userId));
                       if (userBean != null) {
                            String sessionId = SessionConstant.SESSION_ID_PRE
                                    + FormatUtils.password(userBean.getId()
                                            .toString());
                            Map<String, String> cookieSession = new HashMap<String, String>();
                            cookieSession
                            .put(CookieConstant.SESSION_KEY, sessionId);
                            // WrCookie.writeCookie(getResponse(),cookieSession);
                            // 添加用户信息到redis
                            boolean re = redisUtil.addUserInfo( RedisKeyConstant.SESSION + sessionId, BeanUtils.toBean(userBean, UserInfo.class));
                            getSession().setAttribute( SessionConstant.USER_INFO_WEB, BeanUtils.toBean(userBean, UserInfo.class));
                            getSession().setAttribute( DomainConstant.USER_CENTER_KEY, DomainConstant.USER_CENTER);
                            pool.setSession(sessionId);

                            pool.scanSuccess();
                        }else{
                            result = DataResultInfo.getInstance().failure();
                            result.setMessage("用户信息获取异常!请稍后再试");
                        }
                    } else {
                        result = DataResultInfo.getInstance().failure();
                        result.setExtension("11", "用户身份信息失效,请重新登录!");
                    }
                } else {
                    result = DataResultInfo.getInstance().failure();
                    result.setMessage("请求参数有误!");
                    return "error";
                }
                // 不能清除,否则conn方法得不到pool对象,不会进入线程休眠
                // System.out.println("清除扫描过的uuid");
                //PoolCache.cacheMap.remove(uuid);
            }
        } catch (Exception e) {
            Log4jUtil.CommonLog.error("手机扫码 后访问 异常", e);
        }

        sendJsonMessage(result);
        return null;
    }

//扫码成功跳转页
 public String success() {
        String sessionId = WrCookie.getCookie(super.getRequest(), CookieConstant.SESSION_KEY);
        UserInfo userInfo = redisUtil.getUserInfo(RedisKeyConstant.SESSION + sessionId);

       super.getRequest().setAttribute(SessionConstant.USER_INFO_WEB, userInfo);

        return SUCCESS;
    }

//线程判断二维码是否超时
class ScanCounter implements Runnable {

    public Long timeout = 30 * 1000L; //超时时长

    // 传入的对象
    private String uuid;
    private ScanPool scanPool;

    public ScanCounter(String p, ScanPool scanPool) {
        uuid = p;
        this.scanPool = scanPool;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(timeout);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        notifyPool(uuid, scanPool);
    }

    public synchronized void notifyPool(String uuid, ScanPool scanPool) {
        if (scanPool != null) scanPool.notifyPool();
    }

    public String getUuid() {
        return uuid;
    }

    public void setUuid(String uuid) {
        this.uuid = uuid;
    }

    public ScanPool getScanPool() {
        return scanPool;
    }

    public void setScanPool(ScanPool scanPool) {
        this.scanPool = scanPool;
    }



}

ScanPool.java(存放uuid的bean)

public class ScanPool implements Serializable{

    /**
     * @Fields serialVersionUID : TODO(用一句话描述这个变量表示什么) 
     */
    private static final long serialVersionUID = -9117921544228636689L;


    private Object session ;
    //创建时间  
    private Long createTime = System.currentTimeMillis();  

    //登录状态  
    private boolean scanFlag = false;  

    public boolean isScan(){  
        return scanFlag;  
    }  

    public void setScan(boolean scanFlag){  
        this.scanFlag = scanFlag; 
    } 

    /** 
     * 获取扫描状态,如果还没有扫描,则等待固定秒数 
     * @param wiatSecond 需要等待的秒数 
     * @return 
     */  
    public synchronized boolean getScanStatus(){  
        try  
        {  
            if(!isScan()){ //如果还未扫描,则等待  
                this.wait();  
            }  
            if (isScan())  
            {   System.err.println("手机扫描完成设置getScanStatus..true...........");
                return true;  
            }  
        } catch (InterruptedException e)  
        {  
            e.printStackTrace();  
        }  
        return false;  
    }  

    /** 
     * 扫码之后设置扫码状态 
     * @param token 
     * @param id 
     */  
    public synchronized void scanSuccess(){  
        try  
        {  System.err.println("手机扫描完成setScan(true)....同时释放notifyAll(手机扫码时,根据uuid获得的scanpool对象)");
            setScan(true); 
            this.notifyAll();  
        } catch (Exception e)  
        {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }  
    }  

    public synchronized void notifyPool(){  
        try  
        {  
            this.notifyAll();  
        } catch (Exception e)  
        {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }  
    }  

    /***********************************************/
    public Long getCreateTime()  
    {  
        return createTime;  
    }  

    public void setCreateTime(Long createTime)  
    {  
        this.createTime = createTime;  
    }

    public Object getSession() {
        return session;
    }

    public void setSession(Object session) {
        this.session = session;
    }



}

PoolCache.java(定时清理二维码uuid的类)

public class PoolCache {
    // 缓存超时时间 10分钟
    private static Long timeOutSecond = 10 * 60 * 1000L;

    // 每半小时清理一次缓存
    private static Long cleanIntervalSecond = 30 * 60 * 1000L;

    //此map在多线程中会出现 ConcurrentModificationException
    //public static Map<String, ScanPool> cacheMap = new HashMap<String, ScanPool>();

    //List
    //public static CopyOnWriteArrayList<Map<String, ScanPool>> copyOnWriteArrayList = new CopyOnWriteArrayList<Map<String,ScanPool>>();

    //专用于高并发的map类-----Map的并发处理(ConcurrentHashMap)
    public static ConcurrentHashMap<String, ScanPool> cacheMap = new ConcurrentHashMap<String, ScanPool>();


    static {
        new Thread(new Runnable() {

            @Override
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(cleanIntervalSecond);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    clean();
                }
            }

            public void clean() {

                    try {

                        /*if (copyOnWriteArrayList.size() > 0) {
                            Iterator<Map<String, ScanPool>> iterator = copyOnWriteArrayList.iterator();
                            while (iterator.hasNext()) {
                                Map<String, ScanPool> map = iterator.next();
                                Iterator<String> it2 = map.keySet().iterator();
                                while (it2.hasNext()){
                                    String uuid = it2.next();
                                    ScanPool pool = map.get(uuid);
                                    if (System.currentTimeMillis() - pool.getCreateTime() > timeOutSecond ) {
                                        copyOnWriteArrayList.remove(map);
                                        System.err.println("失效了:   ..  "+ uuid);
                                        System.err.println("失效了:   ..  "+ map);
                                        break;
                                    }
                                }
                            }
                        }*/

                        if (cacheMap.keySet().size() > 0) {
                            Iterator<String> iterator = cacheMap.keySet().iterator();
                            while (iterator.hasNext()) {
                                String key = iterator.next();
                                ScanPool pool = cacheMap.get(key);
                                if (System.currentTimeMillis() - pool.getCreateTime() > timeOutSecond ) {
                                    cacheMap.remove(key);
                                }
                            }
                        }
                    } catch (Exception e) {
                        Log4jUtil.CommonLog.error("定时清理uuid异常", e);
                    }
            }
        }).start();
    }

}

扫码流程 图:

流程图:

Created with Raphaël 2.1.2index.jsp(进入扫码页面)循环请求服务器,检查是否被扫pool.doA:扫描成功success.do ; B:二维码超时,清空超时的uuid,刷新页面重新获取新的二维码以及uuidsuccess.jsp(扫码成功跳转页)yes

使用线程实时监听扫码状态;
用户扫描二维码相当于使用 用户名密码 在网页端登录,需要存浏览器cookie
,而用户通过使用手机扫码,直接请求服务器,登陆成功,js中得到用户数据及cookie,把cookie返给页面,再通过js存入cookie中,

本文借鉴与此大神的代码,稍作了修改,参考 扫二维码自动跳转(java),


++++++++demo源码地址:

**应大佬们的要求
附上github源码地址供大家参考*: github.com/luuuuuuuuu/…