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;
                                }
                        }  
                });
        };


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章