在使用FineReport報(bào)表系統(tǒng)中,處于賬戶安全考慮,有些企業(yè)希望同一賬號(hào)在任意時(shí)刻智能在統(tǒng)一客戶端登錄。那么當(dāng)A用戶在C1客戶端登陸后,該賬號(hào)又在另外一個(gè)C2客戶端登陸,服務(wù)器如何取判斷呢?
開發(fā)原理
當(dāng)服務(wù)器在得知A在C1登陸后,在cookie里面寫入一個(gè)標(biāo)識(shí)ID~將瀏覽器標(biāo)記,然后以后的訪問自然就能夠根據(jù)匹配用戶名和對(duì)應(yīng)的標(biāo)記來確定這個(gè)用戶是不是在換瀏覽器登陸了,當(dāng)匹配到用戶異地登陸,就要把之前已經(jīng)登陸的用戶先登出,再登陸新請(qǐng)求的用戶。當(dāng)然關(guān)閉頁面事件里要向后臺(tái)先發(fā)送一個(gè)請(qǐng)求,后臺(tái)要記得清除改用戶標(biāo)記的緩存。
那么客戶端怎么知道自己的賬號(hào)在異地登陸了呢?
這個(gè)就要基于心跳了~因?yàn)槲覀兊膆ttp不是長連接的,所以只能模擬了,弄一個(gè)輪詢ajax不斷的問服務(wù)器,我是否在異地登陸,因?yàn)橹胺?wù)器任何一個(gè)賬號(hào)登陸都會(huì)又一個(gè)ID標(biāo)識(shí),那么當(dāng)接收到一個(gè)客戶端心跳時(shí),我們只要拿出里面的ID和用戶名跟保存的匹配~匹配到存在該用戶名,但是ID不對(duì),那說明一定是另外一個(gè)客戶端登陸了這個(gè)賬號(hào)了,這個(gè)時(shí)候就告知客戶端,你的賬號(hào)已經(jīng)異地登陸,然后前端提示刷新就可以了。
如何實(shí)現(xiàn)?
這里要用到FineReport提供的接口,RequestInterceptor
接口內(nèi)容
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.

* 請(qǐng)求攔截器,通過傳遞op和cmd進(jìn)行內(nèi)置請(qǐng)求的攔截

*/


public interface RequestInterceptor extends Mutable, RequestCMDReceiver, Layer
{


String MARK_STRING = "RequestInterceptor";


int CURRENT_LEVEL = 1;

}


相關(guān)引用類
package com.fr.stable.web;


import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;



/** *//**

* Created by richie on 16/8/9.

* 請(qǐng)求接收器

*/


public interface RequestCMDReceiver
{



/** *//**

* cmd參數(shù)值

* @return cmd參數(shù)值

*/

String getCMD();



/** *//**

* 執(zhí)行

* @param req http請(qǐng)求

* @param res http應(yīng)答

* @param sessionID 會(huì)話ID

* @throws Exception 處理失敗則拋出異常

*/

void actionCMD(HttpServletRequest req, HttpServletResponse res,

String sessionID) throws Exception;



/** *//**

* 執(zhí)行請(qǐng)求

* @param req http請(qǐng)求

* @param res http響應(yīng)

* @throws Exception 處理失敗則拋出異常

*/

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

}


注冊(cè)方式
<extra-core>

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

</extra-core>


其中pid的值應(yīng)該和插件的id值一致,通過這樣的注冊(cè)方式,就可以使用自己定義的處理邏輯來覆蓋掉默認(rèn)的登錄驗(yàn)證請(qǐng)求。
以上,通過故意制造報(bào)錯(cuò)的方式我們能夠看到~FR登陸請(qǐng)求都是繼承于
com.fr.fs.web.service.FSLoadLoginAction 這個(gè)類的~、
進(jìn)一步反編譯JAR可以看到~這個(gè)類是繼承于
com.fr.web.core.ActionNoSessionCMD 最后實(shí)現(xiàn) ActionCMD, RequestInterceptor
那么正好,我們的插件主類就可以免去很多自己寫,直接繼承于FSLoadLoginAction就可以用來處理所有的自定義登陸請(qǐng)求
【凡是需要在登陸時(shí)做得事情都可以在這里做】
當(dāng)然actionCMD(HttpServletRequest req, HttpServletResponse res)這個(gè)執(zhí)行方法還是要重寫的~
還有就是protected void signOnSuccess(HttpServletRequest req, HttpServletResponse res, PrintWriter writer, String url)這個(gè)登陸成功之后需要做一些上面說的操作~
下面是兩個(gè)代碼片段,主要就是處理登陸標(biāo)記和登出清除的.
片段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__");

}

}

//如果用戶名不為空且已登錄的列表中不包含該用戶名說明已經(jīng)被踢下線


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
{

//將當(dāng)前時(shí)刻設(shè)置為最近活躍時(shí)刻

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

//后面的用戶登錄成功后需要先將舊的用戶轉(zhuǎn)移到等待刪除的列表中

remove4logout(req);

//將新登錄的用戶添加到已經(jīng)登錄的用戶中

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("警告","您的賬號(hào)已在其他客戶端登陸!\n如非本人授權(quán),請(qǐng)及時(shí)修改密碼!\n3秒后頁面將跳轉(zhuǎn)至登陸頁!");


setTimeout(function()
{

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

},3000);

}


}else
{

active = true;

}

}

});

};
