新增会话管理过滤器

This commit is contained in:
RuoYi 2018-02-25 17:47:27 +08:00
parent 0cbb3a034a
commit 623c70d6fc
15 changed files with 424 additions and 274 deletions

View File

@ -110,10 +110,10 @@ insert into sys_menu values('4', '角色管理', '1', '2', '/system/role/roleLis
insert into sys_menu values('5', '菜单管理', '1', '3', '/system/menu/menuList', 'C', '0', 'system:menu:list', '#', '2018-01-01', '', 'system', '菜单管理菜单'); insert into sys_menu values('5', '菜单管理', '1', '3', '/system/menu/menuList', 'C', '0', 'system:menu:list', '#', '2018-01-01', '', 'system', '菜单管理菜单');
insert into sys_menu values('6', '操作日志', '1', '4', '/system/operlog/operlogList', 'C', '0', 'system:operlog:list', '#', '2018-01-01', '', 'system', '操作日志菜单'); insert into sys_menu values('6', '操作日志', '1', '4', '/system/operlog/operlogList', 'C', '0', 'system:operlog:list', '#', '2018-01-01', '', 'system', '操作日志菜单');
insert into sys_menu values('7', '登录日志', '1', '5', '/system/userlog/userlogList', 'C', '0', 'system:userlog:list', '#', '2018-01-01', '', 'system', '登录日志菜单'); insert into sys_menu values('7', '登录日志', '1', '5', '/system/userlog/userlogList', 'C', '0', 'system:userlog:list', '#', '2018-01-01', '', 'system', '登录日志菜单');
insert into sys_menu values('8', '在线用户', '2', '1', '/monitor/userOnline', 'C', '0', 'monitor:userOnline', '#', '2018-01-01', '', 'system', '在线用户菜单'); insert into sys_menu values('8', '在线用户', '2', '1', '/monitor/online', 'C', '0', 'monitor:online', '#', '2018-01-01', '', 'system', '在线用户菜单');
insert into sys_menu values('9', '数据监控', '2', '2', '/monitor/druid/index.html', 'C', '0', 'monitor:druid:list', '#', '2018-01-01', '', 'system', '数据监控菜单'); insert into sys_menu values('9', '数据监控', '2', '2', '/monitor/druid/index.html', 'C', '0', 'monitor:druid:list', '#', '2018-01-01', '', 'system', '数据监控菜单');
--- 三级用户按钮 --- 三级用户按钮
insert into sys_menu values('10', '用户新增', '3', '1', '/system/user/add', 'F', '0', 'sys:user:add', '#', '2018-01-01', '', 'system', '用户管理新增按钮'); insert into sys_menu values('10', '用户新增', '3', '1', '/system/user/add', 'F', '0', 'sys:user:add', '#', '2018-01-01', '', 'system', '用户管理新增按钮');
insert into sys_menu values('11', '用户修改', '3', '2', '/system/user/update', 'F', '0', 'sys:user:update', '#', '2018-01-01', '', 'system', '用户管理修改按钮'); insert into sys_menu values('11', '用户修改', '3', '2', '/system/user/update', 'F', '0', 'sys:user:update', '#', '2018-01-01', '', 'system', '用户管理修改按钮');
insert into sys_menu values('12', '用户删除', '3', '3', '/system/user/delete', 'F', '0', 'sys:user:delete', '#', '2018-01-01', '', 'system', '用户管理删除按钮'); insert into sys_menu values('12', '用户删除', '3', '3', '/system/user/delete', 'F', '0', 'sys:user:delete', '#', '2018-01-01', '', 'system', '用户管理删除按钮');
insert into sys_menu values('13', '用户查询', '3', '4', '/system/user/select', 'F', '0', 'sys:user:select', '#', '2018-01-01', '', 'system', '用户管理查询按钮'); insert into sys_menu values('13', '用户查询', '3', '4', '/system/user/select', 'F', '0', 'sys:user:select', '#', '2018-01-01', '', 'system', '用户管理查询按钮');
@ -254,22 +254,22 @@ insert into sys_logininfor values(1, 'admin', 0 , '127.0.0.1', 'Chrome 45', 'Win
-- ---------------------------- -- ----------------------------
drop table if exists sys_user_online; drop table if exists sys_user_online;
create table sys_user_online ( create table sys_user_online (
sessionId varchar(100) default '' comment '用户会话id', sessionId varchar(50) default '' comment '用户会话id',
user_id int(11) default 0 comment '用户ID', login_name varchar(50) default '' comment '登录名称',
login_name varchar(50) default '' comment '登录名', dept_name varchar(50) default '' comment '部门名称',
role_name varchar(50) default '' comment '角色名称',
ipaddr varchar(50) default '' comment '登录IP地址', ipaddr varchar(50) default '' comment '登录IP地址',
browser varchar(50) default '' comment '浏览器类型', browser varchar(50) default '' comment '浏览器类型',
os varchar(50) default '' comment '操作系统', os varchar(50) default '' comment '操作系统',
status varchar(10) default '' comment '在线状态', status varchar(10) default '' comment '在线状态on_line在线off_line离线',
start_timestsamp timestamp default current_timestamp comment 'session创建时间', start_timestsamp timestamp default current_timestamp comment 'session创建时间',
last_access_time timestamp default current_timestamp comment 'session最后访问时间', last_access_time timestamp default current_timestamp comment 'session最后访问时间',
timeout int(5) default 0 comment '超时时间', expireTime int(5) default 0 comment '超时时间,单位为分钟',
onlineSession varchar(50) default '' comment '备份的当前用户会话',
primary key (sessionId) primary key (sessionId)
) engine=innodb default charset=utf8; ) engine=innodb default charset=utf8;
insert into sys_user_online(sessionId, user_id, login_name, ipaddr, browser, os, status) insert into sys_user_online(sessionId, login_name, dept_name, login_name, ipaddr, browser, os, status)
values('c3b252c3-2229-4be4-a5f7-7aba4b0c314c', 1, 'admin', '127.0.0.1', 'Chrome 45', 'Windows 7', 'on_line'); values('c3b252c3-2229-4be4-a5f7-7aba4b0c314c', 'admin', '开发部', '管理员', '127.0.0.1', 'Chrome 45', 'Windows 7');
-- 用户部门表 -- 用户部门表
SELECT * FROM sys_dept; SELECT * FROM sys_dept;

View File

@ -1,6 +1,9 @@
package com.ruoyi.framework.config; package com.ruoyi.framework.config;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map;
import javax.servlet.Filter;
import org.apache.shiro.cache.ehcache.EhCacheManager; import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.mgt.SecurityManager;
@ -20,6 +23,8 @@ import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.project.shiro.realm.UserRealm; import com.ruoyi.project.shiro.realm.UserRealm;
import com.ruoyi.project.shiro.session.OnlineSessionDAO; import com.ruoyi.project.shiro.session.OnlineSessionDAO;
import com.ruoyi.project.shiro.session.OnlineSessionFactory; import com.ruoyi.project.shiro.session.OnlineSessionFactory;
import com.ruoyi.project.shiro.web.OnlineSessionFilter;
import com.ruoyi.project.shiro.web.sync.SyncOnlineSessionFilter;
import com.ruoyi.project.system.menu.service.MenuServiceImpl; import com.ruoyi.project.system.menu.service.MenuServiceImpl;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect; import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
@ -140,12 +145,43 @@ public class ShiroConfig
// 系统权限列表 // 系统权限列表
MenuServiceImpl menuService = SpringUtils.getBean(MenuServiceImpl.class); MenuServiceImpl menuService = SpringUtils.getBean(MenuServiceImpl.class);
filterChainDefinitionMap.putAll(menuService.selectPermsAll()); filterChainDefinitionMap.putAll(menuService.selectPermsAll());
Map<String, Filter> filters = new LinkedHashMap<>();
filters.put("onlineSession", onlineSessionFilter());
filters.put("syncOnlineSession", syncOnlineSessionFilter());
shiroFilterFactoryBean.setFilters(filters);
// 所有请求需要认证 // 所有请求需要认证
filterChainDefinitionMap.put("/**", "authc"); filterChainDefinitionMap.put("/**", "authc");
// 系统请求记录当前会话
filterChainDefinitionMap.put("/system/**", "onlineSession,syncOnlineSession");
filterChainDefinitionMap.put("/monitor/**", "onlineSession,syncOnlineSession");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean; return shiroFilterFactoryBean;
} }
/**
* 自定义在线用户处理过滤器
*/
@Bean
OnlineSessionFilter onlineSessionFilter()
{
OnlineSessionFilter onlineSessionFilter = new OnlineSessionFilter();
onlineSessionFilter.setLoginUrl("/login");
return onlineSessionFilter;
}
/**
* 自定义在线用户同步过滤器
*/
@Bean
SyncOnlineSessionFilter syncOnlineSessionFilter()
{
SyncOnlineSessionFilter syncOnlineSessionFilter = new SyncOnlineSessionFilter();
return syncOnlineSessionFilter;
}
/** /**
* 保证实现了Shiro内部lifecycle函数的bean执行 * 保证实现了Shiro内部lifecycle函数的bean执行
*/ */

View File

@ -77,35 +77,22 @@ public class OnlineSessionDAO extends EnterpriseCacheSessionDAO
/** /**
* 更新会话如更新会话最后访问时间/停止会话/设置超时时间/设置移除属性等会调用 * 更新会话如更新会话最后访问时间/停止会话/设置超时时间/设置移除属性等会调用
*/ */
@Override public void syncToDb(OnlineSession onlineSession)
protected void doUpdate(Session session)
{ {
HttpServletRequest request = HttpContextUtils.getHttpServletRequest(); HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
String uri = request.getServletPath(); String uri = request.getServletPath();
// 如果是静态文件则不更新SESSION System.out.println("===============update================" + uri);
if (checkStaticLink(uri)) Date lastSyncTimestamp = (Date) onlineSession.getAttribute(LAST_SYNC_DB_TIMESTAMP);
{
return;
}
System.out.println("==============update url=================" + uri);
if (session == null)
{
return;
}
OnlineSession onlineSession = (OnlineSession) session;
Date lastSyncTimestamp = (Date) session.getAttribute(LAST_SYNC_DB_TIMESTAMP);
if (lastSyncTimestamp != null) if (lastSyncTimestamp != null)
{ {
boolean needSync = true; boolean needSync = true;
long deltaTime = session.getLastAccessTime().getTime() - lastSyncTimestamp.getTime(); long deltaTime = onlineSession.getLastAccessTime().getTime() - lastSyncTimestamp.getTime();
if (deltaTime < dbSyncPeriod) if (deltaTime < dbSyncPeriod)
{ {
// 时间差不足 无需同步 // 时间差不足 无需同步
needSync = false; needSync = false;
} }
boolean isGuest = session.getId() == null; boolean isGuest = onlineSession.getUserId() == null || onlineSession.getUserId() == 0L;
// 如果不是游客 且session 数据变更了 同步 // 如果不是游客 且session 数据变更了 同步
if (isGuest == false && onlineSession.isAttributeChanged()) if (isGuest == false && onlineSession.isAttributeChanged())
@ -118,8 +105,7 @@ public class OnlineSessionDAO extends EnterpriseCacheSessionDAO
return; return;
} }
} }
session.setAttribute(LAST_SYNC_DB_TIMESTAMP, session.getLastAccessTime()); onlineSession.setAttribute(LAST_SYNC_DB_TIMESTAMP, onlineSession.getLastAccessTime());
// session.setTimeout(expireTime);
// 更新完后 重置标识 // 更新完后 重置标识
if (onlineSession.isAttributeChanged()) if (onlineSession.isAttributeChanged())
{ {
@ -134,14 +120,16 @@ public class OnlineSessionDAO extends EnterpriseCacheSessionDAO
@Override @Override
protected void doDelete(Session session) protected void doDelete(Session session)
{ {
System.out.println("===============delete================"); HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
String uri = request.getServletPath();
System.out.println("===============delete================" + uri);
OnlineSession onlineSession = (OnlineSession) session; OnlineSession onlineSession = (OnlineSession) session;
if (null == onlineSession) if (null == onlineSession)
{ {
return; return;
} }
String sessionId = String.valueOf(onlineSession.getId()); onlineSession.setStatus(OnlineSession.OnlineStatus.off_line);
onlineService.deleteByOnlineId(sessionId); onlineService.deleteByOnlineId(String.valueOf(onlineSession.getId()));
} }
public long getExpireTime() public long getExpireTime()
@ -163,7 +151,8 @@ public class OnlineSessionDAO extends EnterpriseCacheSessionDAO
{ {
boolean linkFlag = false; boolean linkFlag = false;
// 如果是登录请求则不更新SESSION // 如果是登录请求则不更新SESSION
if (StringUtils.endsWithAny(uri, new String[] { "/login", "/logout", "/index", "/", "/favicon.ico" })) if (StringUtils.startsWithAny(uri,
new String[] { "/monitor/online/forceLogout", "/login", "/logout", "/index", "/favicon.ico" }))
{ {
linkFlag = true; linkFlag = true;
} }

View File

@ -0,0 +1,95 @@
package com.ruoyi.project.shiro.web;
import java.io.IOException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.beans.factory.annotation.Autowired;
import com.ruoyi.common.utils.security.ShiroUtils;
import com.ruoyi.project.shiro.ShiroConstants;
import com.ruoyi.project.shiro.session.OnlineSessionDAO;
import com.ruoyi.project.system.online.domain.OnlineSession;
import com.ruoyi.project.system.user.domain.User;
public class OnlineSessionFilter extends AccessControlFilter
{
/**
* 强制退出后重定向的地址
*/
private String forceLogoutUrl = "/login";
@Autowired
private OnlineSessionDAO onlineSessionDAO;
public String getForceLogoutUrl()
{
return forceLogoutUrl;
}
/**
* 表示是否允许访问mappedValue就是[urls]配置中拦截器参数部分如果允许访问返回true否则false
*/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)
throws Exception
{
Subject subject = getSubject(request, response);
if (subject == null || subject.getSession() == null)
{
return true;
}
Session session = onlineSessionDAO.readSession(subject.getSession().getId());
if (session != null && session instanceof OnlineSession)
{
OnlineSession onlineSession = (OnlineSession) session;
request.setAttribute(ShiroConstants.ONLINE_SESSION, onlineSession);
// 把user对象设置进去
boolean isGuest = onlineSession.getUserId() == null || onlineSession.getUserId() == 0L;
if (isGuest == true)
{
User user = ShiroUtils.getUser();
if (user != null)
{
onlineSession.setUserId(user.getUserId());
onlineSession.setLoginName(user.getLoginName());
onlineSession.setDeptName(user.getDept().getDeptName());
onlineSession.setRoleName(user.getRole().getRoleName());
onlineSession.markAttributeChanged();
}
}
if (onlineSession.getStatus() == OnlineSession.OnlineStatus.off_line)
{
return false;
}
}
return true;
}
/**
* 表示当访问拒绝时是否已经处理了如果返回true表示需要继续处理如果返回false表示该拦截器实例已经处理了将直接返回即可
*/
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception
{
Subject subject = getSubject(request, response);
if (subject != null)
{
subject.logout();
}
saveRequestAndRedirectToLogin(request, response);
return true;
}
// 跳转到登录页
protected void redirectToLogin(ServletRequest request, ServletResponse response) throws IOException
{
String loginUrl = getForceLogoutUrl();
WebUtils.issueRedirect(request, response, loginUrl);
}
}

View File

@ -0,0 +1,35 @@
package com.ruoyi.project.shiro.web.sync;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.apache.shiro.web.filter.PathMatchingFilter;
import org.springframework.beans.factory.annotation.Autowired;
import com.ruoyi.project.shiro.ShiroConstants;
import com.ruoyi.project.shiro.session.OnlineSessionDAO;
import com.ruoyi.project.system.online.domain.OnlineSession;
public class SyncOnlineSessionFilter extends PathMatchingFilter
{
@Autowired
private OnlineSessionDAO onlineSessionDAO;
/**
* 同步会话数据到DB 一次请求最多同步一次 防止过多处理 需要放到Shiro过滤器之前
*
* @param request
* @param response
* @return
* @throws Exception
*/
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception
{
OnlineSession session = (OnlineSession) request.getAttribute(ShiroConstants.ONLINE_SESSION);
// 如果session stop了 也不同步
if (session != null && session.getUserId() != null && session.getStopTimestamp() == null)
{
onlineSessionDAO.syncToDb(session);
}
return true;
}
}

View File

@ -1,22 +1,26 @@
package com.ruoyi.project.system.online.controller; package com.ruoyi.project.system.online.controller;
import java.util.List; import java.util.List;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
import com.ruoyi.common.tools.StringTools; import com.ruoyi.common.tools.StringTools;
import com.ruoyi.common.utils.TableDataInfo;
import com.ruoyi.framework.core.controller.BaseController; import com.ruoyi.framework.core.controller.BaseController;
import com.ruoyi.framework.core.domain.R; import com.ruoyi.framework.core.domain.R;
import com.ruoyi.project.shiro.session.OnlineSessionDAO;
import com.ruoyi.project.system.online.domain.OnlineSession;
import com.ruoyi.project.system.online.domain.UserOnline; import com.ruoyi.project.system.online.domain.UserOnline;
import com.ruoyi.project.system.online.service.IUserOnlineService; import com.ruoyi.project.system.online.service.IUserOnlineService;
@Controller @Controller
@RequestMapping("/monitor/userOnline") @RequestMapping("/monitor/online")
public class UserOnlineController extends BaseController public class UserOnlineController extends BaseController
{ {
@ -25,19 +29,21 @@ public class UserOnlineController extends BaseController
@Autowired @Autowired
private IUserOnlineService userOnlineService; private IUserOnlineService userOnlineService;
@Autowired
private OnlineSessionDAO onlineSessionDAO;
@GetMapping() @GetMapping()
public String userOnline() public String online()
{ {
return prefix + "/online"; return prefix + "/online";
} }
@GetMapping("/list") @GetMapping("/list")
@ResponseBody @ResponseBody
public TableDataInfo list(Model model) public List<UserOnline> list(Model model)
{ {
List<UserOnline> list = userOnlineService.selectUserOnlines(); List<UserOnline> list = userOnlineService.selectUserOnlines();
TableDataInfo tableDataInfo = new TableDataInfo(list, 12); return list;
return tableDataInfo;
} }
@GetMapping("/forceLogout") @GetMapping("/forceLogout")
@ -68,4 +74,32 @@ public class UserOnlineController extends BaseController
} }
} }
@ResponseBody
@RequestMapping("/forceLogout/{sessionId}")
public R forceLogout(@PathVariable("sessionId") String sessionId)
{
try
{
UserOnline online = userOnlineService.selectByOnlineId(sessionId);
if (online == null)
{
return R.error("用户已下线。数据不存在");
}
OnlineSession onlineSession = (OnlineSession) onlineSessionDAO.readSession(online.getSessionId());
if (onlineSession == null)
{
return R.error("用户已下线。会话不存在");
}
onlineSession.setStatus(OnlineSession.OnlineStatus.off_line);
online.setStatus(OnlineSession.OnlineStatus.off_line);
userOnlineService.saveByOnline(online);
return R.ok();
}
catch (Exception e)
{
return R.error(e.getMessage());
}
}
} }

View File

@ -1,8 +1,5 @@
package com.ruoyi.project.system.online.domain; package com.ruoyi.project.system.online.domain;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import org.apache.shiro.session.mgt.SimpleSession; import org.apache.shiro.session.mgt.SimpleSession;
/** /**
@ -11,48 +8,20 @@ import org.apache.shiro.session.mgt.SimpleSession;
public class OnlineSession extends SimpleSession public class OnlineSession extends SimpleSession
{ {
// Serialization reminder: private static final long serialVersionUID = 1L;
// You _MUST_ change this number if you introduce a change to this class
// that is NOT serialization backwards compatible. Serialization-compatible
// changes do not require a change to this number. If you need to generate
// a new number in this case, use the JDK's 'serialver' program to generate
// it.
private static final long serialVersionUID = -7125642695178165650L;
static int bitIndexCounter = 0;
private static final int USER_ID_BIT_MASK = 1 << bitIndexCounter++;
private static final int USER_AGENT_BIT_MASK = 1 << bitIndexCounter++;
private static final int STATUS_BIT_MASK = 1 << bitIndexCounter++;
private static final int USERNAME_BIT_MASK = 1 << bitIndexCounter++;
// private static final int REMEMBER_ME_BIT_MASK = 1 << bitIndexCounter++;
public static enum OnlineStatus // 用户ID
{ private Long userId;
on_line("在线"), hidden("隐身"), force_logout("强制退出");
private final String info;
private OnlineStatus(String info) // 用户名称
{
this.info = info;
}
public String getInfo()
{
return info;
}
@Override
public String toString()
{
return super.toString();
}
}
// 当前登录的用户Id
private String userId;
// 登录名
private String loginName; private String loginName;
// 部门名称
private String deptName;
// 角色名称
private String roleName;
// 登录IP地址 // 登录IP地址
private String host; private String host;
@ -65,35 +34,8 @@ public class OnlineSession extends SimpleSession
// 在线状态 // 在线状态
private OnlineStatus status = OnlineStatus.on_line; private OnlineStatus status = OnlineStatus.on_line;
public OnlineSession() // 属性是否改变 优化session数据同步
{ private transient boolean attributeChanged = false;
super();
}
public OnlineSession(String host)
{
super(host);
}
public String getUserId()
{
return userId;
}
public void setUserId(String userId)
{
this.userId = userId;
}
public String getLoginName()
{
return loginName;
}
public void setLoginName(String loginName)
{
this.loginName = loginName;
}
public String getHost() public String getHost()
{ {
@ -125,6 +67,46 @@ public class OnlineSession extends SimpleSession
this.os = os; this.os = os;
} }
public Long getUserId()
{
return userId;
}
public void setUserId(Long userId)
{
this.userId = userId;
}
public String getLoginName()
{
return loginName;
}
public void setLoginName(String loginName)
{
this.loginName = loginName;
}
public String getDeptName()
{
return deptName;
}
public void setDeptName(String deptName)
{
this.deptName = deptName;
}
public String getRoleName()
{
return roleName;
}
public void setRoleName(String roleName)
{
this.roleName = roleName;
}
public OnlineStatus getStatus() public OnlineStatus getStatus()
{ {
return status; return status;
@ -135,11 +117,6 @@ public class OnlineSession extends SimpleSession
this.status = status; this.status = status;
} }
/**
* 属性是否改变 优化session数据同步
*/
private transient boolean attributeChanged = false;
public void markAttributeChanged() public void markAttributeChanged()
{ {
this.attributeChanged = true; this.attributeChanged = true;
@ -167,101 +144,20 @@ public class OnlineSession extends SimpleSession
return super.removeAttribute(key); return super.removeAttribute(key);
} }
/** public static enum OnlineStatus
* Serializes this object to the specified output stream for JDK Serialization.
*
* @param out output stream used for Object serialization.
* @throws java.io.IOException if any of this object's fields cannot be written to the stream.
* @since 1.0
*/
private void writeObject(ObjectOutputStream out) throws IOException
{ {
out.defaultWriteObject(); on_line("在线"), off_line("离线");
short alteredFieldsBitMask = getAlteredFieldsBitMask(); private final String info;
out.writeShort(alteredFieldsBitMask);
if (userId != null) private OnlineStatus(String info)
{ {
out.writeObject(userId); this.info = info;
}
if (browser != null)
{
out.writeObject(browser);
}
if (status != null)
{
out.writeObject(status);
} }
if (loginName != null) public String getInfo()
{ {
out.writeObject(loginName); return info;
} }
} }
/**
* Reconstitutes this object based on the specified InputStream for JDK Serialization.
*
* @param in the input stream to use for reading data to populate this object.
* @throws IOException if the input stream cannot be used.
* @throws ClassNotFoundException if a required class needed for instantiation is not available in the present JVM
* @since 1.0
*/
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException
{
in.defaultReadObject();
short bitMask = in.readShort();
if (isFieldPresent(bitMask, USER_ID_BIT_MASK))
{
this.userId = (String) in.readObject();
}
if (isFieldPresent(bitMask, USER_AGENT_BIT_MASK))
{
this.browser = (String) in.readObject();
}
if (isFieldPresent(bitMask, STATUS_BIT_MASK))
{
this.status = (OnlineStatus) in.readObject();
}
if (isFieldPresent(bitMask, USERNAME_BIT_MASK))
{
this.loginName = (String) in.readObject();
}
}
/**
* Returns a bit mask used during serialization indicating which fields have been serialized. Fields that have been
* altered (not null and/or not retaining the class defaults) will be serialized and have 1 in their respective
* index, fields that are null and/or retain class default values have 0.
*
* @return a bit mask used during serialization indicating which fields have been serialized.
* @since 1.0
*/
private short getAlteredFieldsBitMask()
{
int bitMask = 0;
bitMask = userId != null ? bitMask | USER_ID_BIT_MASK : bitMask;
bitMask = browser != null ? bitMask | USER_AGENT_BIT_MASK : bitMask;
bitMask = status != null ? bitMask | STATUS_BIT_MASK : bitMask;
bitMask = loginName != null ? bitMask | USERNAME_BIT_MASK : bitMask;
return (short) bitMask;
}
/**
* Returns {@code true} if the given {@code bitMask} argument indicates that the specified field has been serialized
* and therefore should be read during deserialization, {@code false} otherwise.
*
* @param bitMask the aggregate bitmask for all fields that have been serialized. Individual bits represent the
* fields that have been serialized. A bit set to 1 means that corresponding field has been serialized, 0
* means it hasn't been serialized.
* @param fieldBitMask the field bit mask constant identifying which bit to inspect (corresponds to a class
* attribute).
* @return {@code true} if the given {@code bitMask} argument indicates that the specified field has been serialized
* and therefore should be read during deserialization, {@code false} otherwise.
* @since 1.0
*/
private static boolean isFieldPresent(short bitMask, int fieldBitMask)
{
return (bitMask & fieldBitMask) != 0;
}
} }

View File

@ -2,8 +2,7 @@ package com.ruoyi.project.system.online.domain;
import java.io.Serializable; import java.io.Serializable;
import java.util.Date; import java.util.Date;
import com.ruoyi.project.system.online.domain.OnlineSession.OnlineStatus;
import com.ruoyi.common.utils.security.ShiroUtils;
/** /**
* 当前在线会话 sys_user_online * 当前在线会话 sys_user_online
@ -17,14 +16,17 @@ public class UserOnline implements Serializable
// 用户会话id // 用户会话id
private String sessionId; private String sessionId;
// 当前登录的用户Id // 部门名称
private String userId; private String deptName;
// 登录名 // 登录名
private String loginName; private String loginName;
// 角色名称
private String roleName;
// 登录IP地址 // 登录IP地址
private String host; private String ipaddr;
// 浏览器类型 // 浏览器类型
private String browser; private String browser;
@ -32,17 +34,17 @@ public class UserOnline implements Serializable
// 操作系统 // 操作系统
private String os; private String os;
// 在线状态 // session创建时间
private OnlineSession.OnlineStatus status = OnlineSession.OnlineStatus.on_line;
// 在线状态
private Date startTimestamp; private Date startTimestamp;
// session最后访问时间 // session最后访问时间
private Date lastAccessTime; private Date lastAccessTime;
// 超时时间 // 超时时间单位为分钟
private Long timeout; private Long expireTime;
// 在线状态
private OnlineStatus status = OnlineStatus.on_line;
// 备份的当前用户会话 // 备份的当前用户会话
private OnlineSession session; private OnlineSession session;
@ -57,14 +59,14 @@ public class UserOnline implements Serializable
this.sessionId = sessionId; this.sessionId = sessionId;
} }
public String getUserId() public String getDeptName()
{ {
return userId; return deptName;
} }
public void setUserId(String userId) public void setDeptName(String deptName)
{ {
this.userId = userId; this.deptName = deptName;
} }
public String getLoginName() public String getLoginName()
@ -77,14 +79,24 @@ public class UserOnline implements Serializable
this.loginName = loginName; this.loginName = loginName;
} }
public String getHost() public String getRoleName()
{ {
return host; return roleName;
} }
public void setHost(String host) public void setRoleName(String roleName)
{ {
this.host = host; this.roleName = roleName;
}
public String getIpaddr()
{
return ipaddr;
}
public void setIpaddr(String ipaddr)
{
this.ipaddr = ipaddr;
} }
public String getBrowser() public String getBrowser()
@ -107,16 +119,6 @@ public class UserOnline implements Serializable
this.os = os; this.os = os;
} }
public OnlineSession.OnlineStatus getStatus()
{
return status;
}
public void setStatus(OnlineSession.OnlineStatus status)
{
this.status = status;
}
public Date getStartTimestamp() public Date getStartTimestamp()
{ {
return startTimestamp; return startTimestamp;
@ -137,14 +139,24 @@ public class UserOnline implements Serializable
this.lastAccessTime = lastAccessTime; this.lastAccessTime = lastAccessTime;
} }
public Long getTimeout() public Long getExpireTime()
{ {
return timeout; return expireTime;
} }
public void setTimeout(Long timeout) public void setExpireTime(Long expireTime)
{ {
this.timeout = timeout; this.expireTime = expireTime;
}
public OnlineStatus getStatus()
{
return status;
}
public void setStatus(OnlineStatus status)
{
this.status = status;
} }
public OnlineSession getSession() public OnlineSession getSession()
@ -157,20 +169,24 @@ public class UserOnline implements Serializable
this.session = session; this.session = session;
} }
/**
* 设置session对象
*/
public static final UserOnline fromOnlineSession(OnlineSession session) public static final UserOnline fromOnlineSession(OnlineSession session)
{ {
UserOnline online = new UserOnline(); UserOnline online = new UserOnline();
online.setSessionId(String.valueOf(session.getId())); online.setSessionId(String.valueOf(session.getId()));
online.setUserId(ShiroUtils.getUser().getUserId() + ""); online.setDeptName(session.getDeptName());
online.setLoginName(ShiroUtils.getUser().getLoginName()); online.setLoginName(session.getLoginName());
online.setRoleName(session.getRoleName());
online.setStartTimestamp(session.getStartTimestamp()); online.setStartTimestamp(session.getStartTimestamp());
online.setLastAccessTime(session.getLastAccessTime()); online.setLastAccessTime(session.getLastAccessTime());
online.setTimeout(session.getTimeout()); online.setExpireTime(session.getTimeout());
online.setHost(session.getHost()); online.setIpaddr(session.getHost());
online.setBrowser(session.getBrowser()); online.setBrowser(session.getBrowser());
online.setOs(session.getOs()); online.setOs(session.getOs());
online.setStatus(session.getStatus());
online.setSession(session); online.setSession(session);
return online; return online;
} }

View File

@ -75,7 +75,7 @@ public class UserOnlineServiceImpl implements IUserOnlineService
{ {
return; return;
} }
session.setTimeout(0); session.setTimeout(1000);
userOnlineDao.deleteByOnlineId(sessionId); userOnlineDao.deleteByOnlineId(sessionId);
} }
} }

View File

@ -35,7 +35,7 @@ public class IndexController extends BaseController
} }
// 系统介绍 // 系统介绍
@GetMapping("/main") @GetMapping("/system/main")
public String main() public String main()
{ {
return "main"; return "main";

View File

@ -6,21 +6,20 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<resultMap type="UserOnline" id="UserOnlineResult"> <resultMap type="UserOnline" id="UserOnlineResult">
<id property="sessionId" column="sessionId" /> <id property="sessionId" column="sessionId" />
<result property="userId" column="user_id" />
<result property="loginName" column="login_name" /> <result property="loginName" column="login_name" />
<result property="host" column="ipaddr" /> <result property="deptName" column="dept_name" />
<result property="roleName" column="role_name" />
<result property="ipaddr" column="ipaddr" />
<result property="browser" column="browser" /> <result property="browser" column="browser" />
<result property="os" column="os" /> <result property="os" column="os" />
<result property="status" column="status" /> <result property="status" column="status" />
<result property="startTimestamp" column="start_timestsamp" /> <result property="startTimestamp" column="start_timestsamp" />
<result property="lastAccessTime" column="last_access_time" /> <result property="lastAccessTime" column="last_access_time" />
<result property="timeout" column="timeout" /> <result property="expireTime" column="expireTime" />
<association property="session" javaType="OnlineSession" resultMap="OnlineSessionResult" /> <association property="session" javaType="OnlineSession" resultMap="OnlineSessionResult" />
</resultMap> </resultMap>
<resultMap type="OnlineSession" id="OnlineSessionResult"> <resultMap type="OnlineSession" id="OnlineSessionResult">
<result property="userId" column="user_id" />
<result property="loginName" column="login_name" />
<result property="host" column="ipaddr" /> <result property="host" column="ipaddr" />
<result property="browser" column="browser" /> <result property="browser" column="browser" />
<result property="os" column="os" /> <result property="os" column="os" />
@ -34,8 +33,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</select> </select>
<insert id="saveByOnline" parameterType="UserOnline"> <insert id="saveByOnline" parameterType="UserOnline">
replace into sys_user_online(sessionId, user_id, login_name, ipaddr, browser, os, status, start_timestsamp, last_access_time, timeout) replace into sys_user_online(sessionId, login_name, dept_name, role_name, ipaddr, browser, os, status, start_timestsamp, last_access_time, expireTime)
values (#{sessionId}, #{userId}, #{loginName}, #{host}, #{browser}, #{os}, #{status}, #{startTimestamp}, #{lastAccessTime}, #{timeout}) values (#{sessionId}, #{loginName}, #{deptName}, #{roleName}, #{ipaddr}, #{browser}, #{os}, #{status}, #{startTimestamp}, #{lastAccessTime}, #{expireTime})
</insert> </insert>
<delete id="deleteByOnlineId" parameterType="String"> <delete id="deleteByOnlineId" parameterType="String">

View File

@ -1,4 +1,4 @@
var prefix = "/sys/online" var prefix = "/monitor/online"
$(function() { $(function() {
load(); load();
}); });
@ -47,28 +47,32 @@ function load() {
checkbox : true checkbox : true
}, },
{ {
field : 'id', // 列字段名 field : 'sessionId', // 列字段名
title : '号' // 列标题 title : '会话编号' // 列标题
}, },
{ {
field : 'username', field : 'loginName',
title : '用户名' title : '登录名称'
}, },
{ {
field : 'host', field : 'deptName',
title : '部门名称'
},
{
field : 'roleName',
title : '角色名称'
},
{
field : 'ipaddr',
title : '主机' title : '主机'
}, },
{ {
field : 'startTimestamp', field : 'browser',
title : '登录时间' title : '浏览器'
}, },
{ {
field : 'lastAccessTime', field : 'os',
title : '最后访问时间' title : '操作系统'
},
{
field : 'timeout',
title : '过期时间'
}, },
{ {
field : 'status', field : 'status',
@ -82,13 +86,21 @@ function load() {
} }
} }
}, },
{
field : 'startTimestamp',
title : '登录时间'
},
{
field : 'lastAccessTime',
title : '最后访问时间'
},
{ {
title : '操作', title : '操作',
field : 'id', field : 'id',
align : 'center', align : 'center',
formatter : function(value, row, index) { formatter : function(value, row, index) {
var d = '<a class="btn btn-warning btn-sm" href="#" title="删除" mce_href="#" onclick="forceLogout(\'' var d = '<a class="btn btn-warning btn-sm" href="#" title="删除" onclick="forceLogout(\''
+ row.id + row.sessionId
+ '\')"><i class="fa fa-remove"></i></a> '; + '\')"><i class="fa fa-remove"></i></a> ';
return d; return d;
} }

View File

@ -0,0 +1,38 @@
<head th:fragment="header">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title></title>
<meta name="keywords" content="">
<meta name="description" content="">
<link rel="shortcut icon" href="favicon.ico">
<link href="css/bootstrap.min.css?v=3.3.6" th:href="@{/css/bootstrap.min.css?v=3.3.6}" rel="stylesheet">
<link href="/css/font-awesome.css?v=4.4.0" th:href="@{/css/font-awesome.css?v=4.4.0}" rel="stylesheet">
<link href="/css/plugins/bootstrap-table/bootstrap-table.min.css" th:href="@{/css/plugins/bootstrap-table/bootstrap-table.min.css}" rel="stylesheet">
<link href="/css/plugins/jsTree/style.min.css" rel="stylesheet">
<link href="/css/plugins/jqTreeGrid/jquery.treegrid.css" rel="stylesheet">
<!--summernote css -->
<link href="/css/plugins/summernote/summernote-0.8.8.css" rel="stylesheet">
<link href="css/animate.css" th:href="@{/css/animate.css}" rel="stylesheet">
<link href="/css/plugins/chosen/chosen.css" rel="stylesheet">
<link href="/css/style.css?v=4.1.0" th:href="@{/css/style.css?v=4.1.0}" rel="stylesheet">
</head>
<div th:fragment="footer">
<script src="/js/jquery.min.js?v=2.1.4"></script>
<script src="/js/bootstrap.min.js?v=3.3.6"></script>
<script src="/js/plugins/bootstrap-table/bootstrap-table.min.js"></script>
<script src="/js/plugins/bootstrap-table/bootstrap-table-mobile.min.js"></script>
<script
src="/js/plugins/bootstrap-table/locale/bootstrap-table-zh-CN.min.js"></script>
<script src="/js/plugins/validate/jquery.validate.min.js"></script>
<script src="/js/plugins/validate/messages_zh.min.js"></script>
<script src="/js/plugins/jsTree/jstree.min.js"></script>
<script src="/js/plugins/jqTreeGrid/jquery.treegrid.min.js"></script>
<script src="/js/plugins/jqTreeGrid/jquery.treegrid.extension.js"></script>
<script src="/js/plugins/jqTreeGrid/jquery.treegrid.bootstrap3.js"></script>
<script src="/js/plugins/chosen/chosen.jquery.js"></script>
<script src="/js/plugins/layer/layer.js"></script>
<script src="/js/content.js?v=1.0.0"></script>
<!--summernote-->
<script src="/js/plugins/summernote/summernote.js"></script>
<script src="/js/plugins/summernote/summernote-zh-CN.min.js"></script>
</div>

View File

@ -43,7 +43,7 @@
<li class="active"> <li class="active">
<a href="index.html"><i class="fa fa-home"></i> <span class="nav-label">主页</span> <span class="fa arrow"></span></a> <a href="index.html"><i class="fa fa-home"></i> <span class="nav-label">主页</span> <span class="fa arrow"></span></a>
<ul class="nav nav-second-level"> <ul class="nav nav-second-level">
<li class="active"><a class="J_menuItem" th:href="@{/main}">了解若依</a></li> <li class="active"><a class="J_menuItem" th:href="@{/system/main}">了解若依</a></li>
</ul> </ul>
</li> </li>
<li th:each="menu : ${menus}"> <li th:each="menu : ${menus}">
@ -114,7 +114,7 @@
</div> </div>
<div class="row J_mainContent" id="content-main"> <div class="row J_mainContent" id="content-main">
<iframe class="J_iframe" name="iframe0" width="100%" height="100%" <iframe class="J_iframe" name="iframe0" width="100%" height="100%"
th:src="@{/main}" frameborder="0" seamless></iframe> th:src="@{/system/main}" frameborder="0" seamless></iframe>
</div> </div>
<div class="footer"> <div class="footer">
<div class="pull-right">© 2018-2020 RuoYi Copyright</div> <div class="pull-right">© 2018-2020 RuoYi Copyright</div>

View File

@ -28,7 +28,7 @@
<small>移动设备访问请扫描以下二维码:</small> <small>移动设备访问请扫描以下二维码:</small>
<br> <br>
<br> <br>
<img src="img/qr_code.png" width="100%" style="max-width:264px;"> <img src="/img/qr_code.png" width="100%" style="max-width:264px;">
<br> <br>
</div> </div>
<div class="col-sm-5"> <div class="col-sm-5">
@ -146,7 +146,7 @@
<br>如果图片太小,可以点击图片放大。 <br>如果图片太小,可以点击图片放大。
</div> </div>
<p id="pay-qrcode"> <p id="pay-qrcode">
<a href="javascript:;"><img src="img/pay.png" width="100%" alt="请使用手机支付宝或者微信扫码支付"> <a href="javascript:;"><img src="/img/pay.png" width="100%" alt="请使用手机支付宝或者微信扫码支付">
</a> </a>
</p> </p>
@ -155,7 +155,7 @@
</div> </div>
</div> </div>
</div> </div>
<script src="js/jquery.min.js?v=2.1.4"></script> <script src="/js/jquery.min.js?v=2.1.4"></script>
<script src="js/bootstrap.min.js?v=3.3.6"></script> <script src="/js/bootstrap.min.js?v=3.3.6"></script>
</body> </body>
</html> </html>