在使用FineReport报表系统中,处于账户安全考虑,有些企业希望同一账号在任意时刻智能在统一客户端登录。那么当A用户在C1客户端登陆后,该账号又在另外一个C2客户端登陆,服务器如何取判断呢?

开发原理

当服务器在得知A在C1登陆后,在cookie里面写入一个标识ID~将浏览器标记,然后以后的访问自然就能够根据匹配用户名和对应的标记来确定这个用户是不是在换浏览器登陆了,当匹配到用户异地登陆,就要把之前已经登陆的用户先登出,再登陆新请求的用户。当然关闭页面事件里要向后台先发送一个请求,后台要记得清除改用户标记的缓存。

那么客户端怎么知道自己的账号在异地登陆了呢?

这个就要基于心跳了~因为我们的http不是长连接的,所以只能模拟了,弄一个轮询ajax不断的问服务器,我是否在异地登陆,因为之前服务器任何一个账号登陆都会又一个ID标识,那么当接收到一个客户端心跳时,我们只要拿出里面的ID和用户名跟保存的匹配~匹配到存在该用户名,但是ID不对,那说明一定是另外一个客户端登陆了这个账号了,这个时候就告知客户端,你的账号已经异地登陆,然后前端提示刷新就可以了。

如何实现?

这里要用到FineReport提供的接口,RequestInterceptor

接口内容

package com.fr.stable.fun;

import com.fr.stable.fun.mark.Layer;

import com.fr.stable.fun.mark.Mutable;

import com.fr.stable.web.RequestCMDReceiver;

/**

 * Created by richie on 16/8/9.

 * 请求拦截器,通过传递op和cmd进行内置请求的拦截

 */

public interface RequestInterceptor extends Mutable, RequestCMDReceiver, Layer {

    String MARK_STRING = "RequestInterceptor";

    int CURRENT_LEVEL = 1;

}

相关引用类

package com.fr.stable.web;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

/**

 * Created by richie on 16/8/9.

 * 请求接收器

 */

public interface RequestCMDReceiver {

    /**

     * cmd参数值

     * @return cmd参数值

     */

    String getCMD();

    /**

     * 执行

     * @param req http请求

     * @param res http应答

     * @param sessionID 会话ID

     * @throws Exception 处理失败则抛出异常

     */

    void actionCMD(HttpServletRequest req, HttpServletResponse res,

                   String sessionID) throws Exception;

    /**

     * 执行请求

     * @param req http请求

     * @param res http响应

     * @throws Exception 处理失败则抛出异常

     */

    void actionCMD(HttpServletRequest req, HttpServletResponse res) throws Exception;

}

注册方式

<extra-core>

   <RequestInterceptor class="com.fr.plugin.xxx.youclassname" op="fs_load" cmd="login" pid="com.fr.plugin.xxx.name"/>

</extra-core>

其中pid的值应该和插件的id值一致,通过这样的注册方式,就可以使用自己定义的处理逻辑来覆盖掉默认的登录验证请求。

以上,通过故意制造报错的方式我们能够看到~FR登陆请求都是继承于

com.fr.fs.web.service.FSLoadLoginAction 这个类的~、

进一步反编译JAR可以看到~这个类是继承于

com.fr.web.core.ActionNoSessionCMD  最后实现 ActionCMD, RequestInterceptor

那么正好,我们的插件主类就可以免去很多自己写,直接继承于FSLoadLoginAction就可以用来处理所有的自定义登陆请求

【凡是需要在登陆时做得事情都可以在这里做】

当然actionCMD(HttpServletRequest req, HttpServletResponse res)这个执行方法还是要重写的~

还有就是protected void signOnSuccess(HttpServletRequest req, HttpServletResponse res, PrintWriter writer, String url)这个登陆成功之后需要做一些上面说的操作~

下面是两个代码片段,主要就是处理登陆标记和登出清除的.

片段1

@Override

        public void actionCMD(HttpServletRequest req, HttpServletResponse res)

            throws Exception {

                String username = WebUtils.getHTTPRequestParameter(req, Constants.FR_USERNAME);

                String heartBeat = WebUtils.getHTTPRequestParameter(req, "__heartbeat__");

                if(ComparatorUtils.equals(heartBeat, "__active__")){

                        if(StringUtils.isEmpty(username)){

                                username = WebUtils.getHTTPRequestParameter(req, "__username__");

                                if(!StringUtils.isEmpty(username)){

                                        req.getSession(true).removeAttribute("__username__");

                                }

                        }

                        //如果用户名不为空且已登录的列表中不包含该用户名说明已经被踢下线

                        if(!StringUtils.isEmpty(username) && !log.containsKey(username)){

                                writeResult(res,false);

                                return ;

                        }

                        //如果在已登录的列表中找到了该用户名的记录,但是ID不匹配也说明被踢下线了

                        if(log.containsKey(username)){

                                String crtUUID = WebUtils.getHTTPRequestParameter(req, "_sessionid_");

                                SingleLoginBean logBean = log.get(username);

                                String oldId = logBean.getId();

                                if(!ComparatorUtils.equals(crtUUID,oldId)){

                                        writeResult(res,false);

                                        return;

                                }else{

                                        //将当前时刻设置为最近活跃时刻

                                        logBean.setWait4removeTime(new Date().getTime());

                                }

                        }

                        writeResult(res,true);

                        //登出太久不活跃的用户 30S以上

                        checkAllUser();

                        return;

                }

                super.actionCMD(req, res);

        }

片段2

protected void signOnSuccess(HttpServletRequest req, HttpServletResponse res, PrintWriter writer, String url) throws IOException, JSONException {

                String username = WebUtils.getHTTPRequestParameter(req, Constants.FR_USERNAME);

                String uuid = req.getSession(true).getId();

                SingleLoginBean logBean = new SingleLoginBean(uuid,req,res,req.getSession(true));

                logBean.setWait4removeTime(new Date().getTime());

                //后面的用户登录成功后需要先将旧的用户转移到等待删除的列表中

                remove4logout(req);

                //将新登录的用户添加到已经登录的用户中

                log.put(username, logBean);

                if ("true".equals(WebUtils.getHTTPRequestParameter(req, ParameterConsts.__REDIRECT__))) {

            res.sendRedirect(url);

        } else {

            writer.print(JSONObject.create().put("url", url));

        }

    }gf =

下面就是JS轮询了

var askServer4Active = function(){

                var sessionid = getCrtSessionid();

                if( sessionid == "" || sessionid == null ){

                        return ;

                }

                var url = FR.servletURL+"?op=fs_load&cmd=login&__heartbeat__=__active__&_sessionid_="+sessionid;

                FR.ajax({  

                        url: url,  

                        type: "POST",  

                        dataType:"JSON",

                        success: function(msg){  

                                if(!msg.success){

                                        if(active){

                                                active = false;

                                                clearInterval(timer);

                                                FR.Msg.alert("警告","您的账号已在其他客户端登陆!\n如非本人授权,请及时修改密码!\n3秒后页面将跳转至登陆页!");

                                                setTimeout(function(){

                                                        document.location = FR.servletURL+"?op=fs";

                                                },3000);

                                        }

                                }else{

                                        active = true;

                                }

                        }  

                });

        };

  

FineReport:任意时刻只允许在一个客户端登陆账号的插件的更多相关文章

  1. php实现同一时间内一个账户只允许在一个终端登陆

    在账户表的基础上,我新建了一个账户account_session表,用来记录登录账户的account_id和最新一次登录成功用户的session_id,然后首先要修改登录方法:每次登录成功后,要将登录 ...

  2. 简单php实现同一时间内一个账户只允许在一个终端登陆

    在账户表的基础上,我新建了一个账户account_session表,用来记录登录账户的account_id和最新一次登录成功用户的session_id,然后首先要修改登录方法:每次登录成功后,要将登录 ...

  3. 【MySQL8】 安装后的简单配置(主要解决navicat等客户端登陆报错问题)

    一.navicat等客户端登陆报错的原因 使用mysql,多数我们还是喜欢用可视化的客户端登陆管理的,个人比较喜欢用navicat.一般装好服务器以后,习惯建一个远程的登陆帐号,在mysql8服务器上 ...

  4. RAC 10.2.0.5,客户端登陆间断遭遇ORA-12545

    实验环境: 服务端:OEL 5.7 + Oracle 10.2.0.5 RAC  客户端:Windows 7 + Oracle 11.2.0.1 Client   1.客户端登陆间断遭遇ORA-125 ...

  5. Projecet客户端登陆无法通过验证

    客户反映使用Project客户端登陆project服务器的时候,只有域管理员账户才能够登陆成功,其他的账户登陆都无法验证通过,无论是https的方式还是客户端的方式,但是域账户登陆计算机是可以登陆成功 ...

  6. Android 实现两个list分别出现(在某一时刻只出现一个控件)

    第一种方法: 在.xml文件中将这两个List分别放入不同的布局管理器中,比如说 <RelativeLayout android:layout_width="match_parent& ...

  7. 修改TortoiseSVN客户端登陆用户

    TortoiseSVN是一款常用且非常不错的SVN工具,俗称小乌龟.开发的时候,经常用的当然是TortoiseSVN客户端了. 一般情况下,TortoiseSVN服务器提供的IP地址和用户都不会变,而 ...

  8. Oracle之配置客户端登陆多个远程数据库

    一.引言 一直搞不明白Oracle数据库的客户端是怎么回事,怎么配置,前几天由于工作中需要用到Oracle,而且需要连接两个不同的数据库,就通过上网和请教同事终于把客户端的配置搞定了,记录之,学习之 ...

  9. java在线聊天项目 客户端登陆窗口LoginDialog的注册用户功能 修改注册逻辑 增空用户名密码的反馈 增加showMessageDialog()提示框

    LoginDialog类的代码修改如下: package com.swift.frame; import java.awt.EventQueue; import java.awt.event.Acti ...

随机推荐

  1. 在传统.NET Framework 上运行ASP.NET Core项目

    新的项目我们想用ASP.NET Core来开发,但是苦于我们历史的遗产很多,比如<使用 JavaScriptService 在.NET Core 里实现DES加密算法>,我们要估计等到.N ...

  2. SQLServer执行命令出现“目录无效的提示”

    异常处理汇总-数据库系列  http://www.cnblogs.com/dunitian/p/4522990.html 一般都是清理垃圾清理过头了,把不该删的目录删了 网上说法: 问题描述: 1.s ...

  3. 微软新神器-Power BI横空出世,一个简单易用,还用得起的BI产品,你还在等什么???

    在当前互联网,由于大数据研究热潮,以及数据挖掘,机器学习等技术的改进,各种数据可视化图表层出不穷,如何让大数据生动呈现,也成了一个具有挑战性的可能,随之也出现了大量的商业化软件.今天就给大家介绍一款逆 ...

  4. 视频 - 在 VirtualBox 中部署 OpenStack

    大家新年好,CloudMan 今天给大家带来一件新年礼物. 一直以来大家都反馈 OpenStack 学习有两大障碍:1. 实验环境难搭2. 体系复杂,难道大今天我就先帮大家解决环境问题.前两天我抽空在 ...

  5. JavaScript之链式结构序列化

    一.概述 在JavaScript中,链式模式代码,太多太多,如下: if_else: if(...){ //TODO }else if(...){ //TODO }else{ //TODO } swi ...

  6. 谈谈一些有趣的CSS题目(七)-- 消失的边界线问题

    开本系列,谈谈一些有趣的 CSS 题目,题目类型天马行空,想到什么说什么,不仅为了拓宽一下解决问题的思路,更涉及一些容易忽视的 CSS 细节. 解题不考虑兼容性,题目天马行空,想到什么说什么,如果解题 ...

  7. vmware上网的方式

    vmware上网设置 vmware虚拟机上网设置 我的一些心得,如下: 如何使vmware虚拟机中的操作系统能够上网? 第一种情况: 主机使用PPPOE拨号上网 方法一:NAT方式 1.先关闭虚拟机中 ...

  8. ASP.NET SignaiR 实现消息的即时推送,并使用Push.js实现通知

    一.使用背景 1. SignalR是什么? ASP.NET SignalR 是为 ASP.NET 开发人员提供的一个库,可以简化开发人员将实时 Web 功能添加到应用程序的过程.实时 Web 功能是指 ...

  9. vue.js几行实现的简单的todo list

    序:目前前端框架如:vue.react.angular,构建工具fis3.gulp.webpack等等...... 可谓是五花八门,层出不穷,眼花缭乱...其实吧只要你想玩还是可以玩玩的..下面是看了 ...

  10. Android Studio快捷键

      一.android studio 默认快捷键 刚开始接触一款开发软件,想必很想了解它的快捷方式,这会对你的编程起到很好的帮助,提高工作效率,接下来给你介绍下Android Studio一些常用的快 ...