在使用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));

}

}


下面就是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;

}

}

});

};
