UDN-企业互联网技术人气社区

板块导航

浏览  : 304
回复  : 6

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

[复制链接]
小辫儿的头像 楼主
发表于 2016-12-24 14:48:20 | 显示全部楼层 |阅读模式
  在使用FineReport报表系统中,处于账户安全考虑,有些企业希望同一账号在任意时刻智能在统一客户端登录。那么当A用户在C1客户端登陆后,该账号又在另外一个C2客户端登陆,服务器如何取判断呢?

  开发原理

  当服务器在得知A在C1登陆后,在cookie里面写入一个标识ID~将浏览器标记,然后以后的访问自然就能够根据匹配用户名和对应的标记来确定这个用户是不是在换浏览器登陆了,当匹配到用户异地登陆,就要把之前已经登陆的用户先登出,再登陆新请求的用户。当然关闭页面事件里要向后台先发送一个请求,后台要记得清除改用户标记的缓存。

  那么客户端怎么知道自己的账号在异地登陆了呢?

  这个就要基于心跳了~因为我们的http不是长连接的,所以只能模拟了,弄一个轮询ajax不断的问服务器,我是否在异地登陆,因为之前服务器任何一个账号登陆都会又一个ID标识,那么当接收到一个客户端心跳时,我们只要拿出里面的ID和用户名跟保存的匹配~匹配到存在该用户名,但是ID不对,那说明一定是另外一个客户端登陆了这个账号了,这个时候就告知客户端,你的账号已经异地登陆,然后前端提示刷新就可以了。

  如何实现?

  这里要用到FineReport提供的接口,RequestInterceptor

  接口内容
  1. package com.fr.stable.fun;

  2. import com.fr.stable.fun.mark.Layer;
  3. import com.fr.stable.fun.mark.Mutable;
  4. import com.fr.stable.web.RequestCMDReceiver;

  5. /**
  6. * Created by richie on 16/8/9.
  7. * 请求拦截器,通过传递op和cmd进行内置请求的拦截
  8. */
  9. public interface RequestInterceptor extends Mutable, RequestCMDReceiver, Layer {

  10.     String MARK_STRING = "RequestInterceptor";

  11.     int CURRENT_LEVEL = 1;
  12. }
复制代码

  相关引用类
  1. package com.fr.stable.web;

  2. import javax.servlet.http.HttpServletRequest;
  3. import javax.servlet.http.HttpServletResponse;

  4. /**
  5. * Created by richie on 16/8/9.
  6. * 请求接收器
  7. */
  8. public interface RequestCMDReceiver {

  9.     /**
  10.      * cmd参数值
  11.      * @return cmd参数值
  12.      */
  13.     String getCMD();

  14.     /**
  15.      * 执行
  16.      * @param req http请求
  17.      * @param res http应答
  18.      * @param sessionID 会话ID
  19.      * @throws Exception 处理失败则抛出异常
  20.      */
  21.     void actionCMD(HttpServletRequest req, HttpServletResponse res,
  22.                    String sessionID) throws Exception;

  23.     /**
  24.      * 执行请求
  25.      * @param req http请求
  26.      * @param res http响应
  27.      * @throws Exception 处理失败则抛出异常
  28.      */
  29.     void actionCMD(HttpServletRequest req, HttpServletResponse res) throws Exception;
  30. }
复制代码

  注册方式
  1. <extra-core>
  2.    <RequestInterceptor class="com.fr.plugin.xxx.youclassname" op="fs_load" cmd="login" pid="com.fr.plugin.xxx.name"/>
  3. </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
  1. @Override
  2.         public void actionCMD(HttpServletRequest req, HttpServletResponse res)
  3.             throws Exception {
  4.                 String username = WebUtils.getHTTPRequestParameter(req, Constants.FR_USERNAME);
  5.                 String heartBeat = WebUtils.getHTTPRequestParameter(req, "__heartbeat__");
  6.                 if(ComparatorUtils.equals(heartBeat, "__active__")){
  7.                         if(StringUtils.isEmpty(username)){
  8.                                 username = WebUtils.getHTTPRequestParameter(req, "__username__");
  9.                                 if(!StringUtils.isEmpty(username)){
  10.                                         req.getSession(true).removeAttribute("__username__");
  11.                                 }
  12.                         }
  13.                         //如果用户名不为空且已登录的列表中不包含该用户名说明已经被踢下线
  14.                         if(!StringUtils.isEmpty(username) && !log.containsKey(username)){
  15.                                 writeResult(res,false);
  16.                                 return ;
  17.                         }
  18.                         //如果在已登录的列表中找到了该用户名的记录,但是ID不匹配也说明被踢下线了
  19.                         if(log.containsKey(username)){
  20.                                 String crtUUID = WebUtils.getHTTPRequestParameter(req, "_sessionid_");
  21.                                 SingleLoginBean logBean = log.get(username);
  22.                                 String oldId = logBean.getId();
  23.                                 if(!ComparatorUtils.equals(crtUUID,oldId)){
  24.                                         writeResult(res,false);
  25.                                         return;
  26.                                 }else{
  27.                                         //将当前时刻设置为最近活跃时刻
  28.                                         logBean.setWait4removeTime(new Date().getTime());
  29.                                 }
  30.                         }
  31.                         writeResult(res,true);
  32.                         //登出太久不活跃的用户 30S以上
  33.                         checkAllUser();
  34.                         return;
  35.                 }
  36.                 super.actionCMD(req, res);
  37.         }
复制代码

  片段2
  1. protected void signOnSuccess(HttpServletRequest req, HttpServletResponse res, PrintWriter writer, String url) throws IOException, JSONException {
  2.                 String username = WebUtils.getHTTPRequestParameter(req, Constants.FR_USERNAME);
  3.                 String uuid = req.getSession(true).getId();
  4.                 SingleLoginBean logBean = new SingleLoginBean(uuid,req,res,req.getSession(true));
  5.                 logBean.setWait4removeTime(new Date().getTime());
  6.                 //后面的用户登录成功后需要先将旧的用户转移到等待删除的列表中
  7.                 remove4logout(req);
  8.                 //将新登录的用户添加到已经登录的用户中
  9.                 log.put(username, logBean);
  10.                 if ("true".equals(WebUtils.getHTTPRequestParameter(req, ParameterConsts.__REDIRECT__))) {
  11.             res.sendRedirect(url);
  12.         } else {
  13.             writer.print(JSONObject.create().put("url", url));
  14.         }
  15.     }
复制代码

  下面就是JS轮询了
  1. var askServer4Active = function(){
  2.                 var sessionid = getCrtSessionid();
  3.                 if( sessionid == "" || sessionid == null ){
  4.                         return ;
  5.                 }
  6.                 var url = FR.servletURL+"?op=fs_load&cmd=login&__heartbeat__=__active__&_sessionid_="+sessionid;
  7.                 FR.ajax({  
  8.                         url: url,  
  9.                         type: "POST",  
  10.                         dataType:"JSON",
  11.                         success: function(msg){  
  12.                                 if(!msg.success){
  13.                                         if(active){
  14.                                                 active = false;
  15.                                                 clearInterval(timer);
  16.                                                 FR.Msg.alert("警告","您的账号已在其他客户端登陆!\n如非本人授权,请及时修改密码!\n3秒后页面将跳转至登陆页!");
  17.                                                 setTimeout(function(){
  18.                                                         document.location = FR.servletURL+"?op=fs";
  19.                                                 },3000);
  20.                                         }
  21.                                 }else{
  22.                                         active = true;
  23.                                 }
  24.                         }  
  25.                 });
  26.         };
复制代码

相关帖子

发表于 2017-1-5 10:27:08 | 显示全部楼层
个人觉得js是一种解释性语言,它提供了一个非常方便的开发过程,不需要编译,js与HTML标识结合在一起,从而方便用户的使用操作。
使用道具 举报

回复

发表于 2017-1-5 10:27:08 | 显示全部楼层
我是被标题吸引进来的
使用道具 举报

回复

发表于 2017-1-5 10:27:08 | 显示全部楼层
前排支持下
使用道具 举报

回复

发表于 2017-1-5 10:27:08 | 显示全部楼层
LZ敢整点更有创意的不?兄弟们等着围观捏~
使用道具 举报

回复

发表于 2017-1-5 10:27:12 | 显示全部楼层
我是被标题吸引进来的
使用道具 举报

回复

发表于 2017-1-5 10:27:12 | 显示全部楼层
js太强大了,好多工作前端都可以做了…
使用道具 举报

回复

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关于我们
联系我们
  • 电话:010-86393388
  • 邮件:udn@yonyou.com
  • 地址:北京市海淀区北清路68号
移动客户端下载
关注我们
  • 微信公众号:yonyouudn
  • 扫描右侧二维码关注我们
  • 专注企业互联网的技术社区
版权所有:用友网络科技股份有限公司82041 京ICP备05007539号-11 京公网网备安1101080209224 Powered by Discuz!
快速回复 返回列表 返回顶部