首页 > 代码库 > FineReport:任意时刻只允许在一个客户端登陆账号的插件

FineReport:任意时刻只允许在一个客户端登陆账号的插件

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

开发原理

当服务器在得知AC1登陆后,在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));
        }
    }

下面就是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:任意时刻只允许在一个客户端登陆账号的插件