在使用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; }
相關引用類java
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; }
註冊方式web
<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)這個登錄成功以後須要作一些上面說的操做~ 下面是兩個代碼片斷,主要就是處理登錄標記和登出清除的. 片斷1ajax
@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; } } }); };