From bfaf973e5441686c6e1b0817da8001c7fa20ba36 Mon Sep 17 00:00:00 2001 From: RuoYi Date: Sun, 25 Feb 2018 23:42:32 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=BC=9A=E8=AF=9D=E5=AE=9A?= =?UTF-8?q?=E6=97=B6=E6=B8=85=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- new_intall_20180225.sql | 4 +- .../com/ruoyi/common/tools/DateTools.java | 51 ++++++ .../ruoyi/framework/config/DruidDBConfig.java | 1 + .../ruoyi/framework/config/I18nConfig.java | 2 + .../ruoyi/framework/config/ShiroConfig.java | 86 ++++++++-- .../ruoyi/project/shiro/ShiroConstants.java | 4 - .../shiro/session/OnlineSessionDAO.java | 20 +-- .../shiro/session/OnlineSessionFactory.java | 15 +- .../online}/OnlineSessionFilter.java | 4 +- .../sync/SyncOnlineSessionFilter.java | 7 +- .../web/session/OnlineWebSessionManager.java | 156 ++++++++++++++++++ .../SpringSessionValidationScheduler.java | 143 ++++++++++++++++ .../service/LogininforServiceImpl.java | 1 + .../system/online/dao/IUserOnlineDao.java | 9 +- .../system/online/dao/UserOnlineDaoImpl.java | 23 +++ .../system/online/domain/OnlineSession.java | 2 + .../online/service/IUserOnlineService.java | 16 ++ .../online/service/UserOnlineServiceImpl.java | 40 +++++ src/main/resources/application-druid.yml | 4 +- src/main/resources/application.yml | 11 +- .../mybatis/system/SystemOnlineMapper.xml | 5 + 21 files changed, 565 insertions(+), 39 deletions(-) create mode 100644 src/main/java/com/ruoyi/common/tools/DateTools.java rename src/main/java/com/ruoyi/project/shiro/web/{ => filter/online}/OnlineSessionFilter.java (95%) rename src/main/java/com/ruoyi/project/shiro/web/{ => filter}/sync/SyncOnlineSessionFilter.java (88%) create mode 100644 src/main/java/com/ruoyi/project/shiro/web/session/OnlineWebSessionManager.java create mode 100644 src/main/java/com/ruoyi/project/shiro/web/session/SpringSessionValidationScheduler.java diff --git a/new_intall_20180225.sql b/new_intall_20180225.sql index ab66a02fe..32bac40fa 100644 --- a/new_intall_20180225.sql +++ b/new_intall_20180225.sql @@ -268,8 +268,8 @@ create table sys_user_online ( primary key (sessionId) ) engine=innodb default charset=utf8; -insert into sys_user_online(sessionId, login_name, dept_name, login_name, ipaddr, browser, os, status) -values('c3b252c3-2229-4be4-a5f7-7aba4b0c314c', 'admin', '开发部', '管理员', '127.0.0.1', 'Chrome 45', 'Windows 7'); +insert into sys_user_online(sessionId, login_name, dept_name, role_name, ipaddr, browser, os, status) +values('c3b252c3-2229-4be4-a5f7-7aba4b0c314c', 'admin', '开发部', '管理员', '127.0.0.1', 'Chrome 45', 'Windows 7', 'on_line'); -- 用户部门表 SELECT * FROM sys_dept; diff --git a/src/main/java/com/ruoyi/common/tools/DateTools.java b/src/main/java/com/ruoyi/common/tools/DateTools.java new file mode 100644 index 000000000..d9f5ee21f --- /dev/null +++ b/src/main/java/com/ruoyi/common/tools/DateTools.java @@ -0,0 +1,51 @@ +package com.ruoyi.common.tools; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +public class DateTools +{ + + public static final String DEFAULT_YYYYMMDD = "yyyyMMddHHmmss"; + + public static final String DEFAULT_YYYY_MM_DD = "yyyy-MM-dd HH:mm:ss"; + + public static final String dateTimeStr() + { + return dateTimeNow(DEFAULT_YYYY_MM_DD); + } + + public static final String dateTimeNow() + { + return dateTimeNow(DEFAULT_YYYYMMDD); + } + + public static final String dateTimeNow(final String format) + { + return dateTime(format, new Date()); + } + + public static final String dateTime(final Date date) + { + return dateTime(DEFAULT_YYYYMMDD, date); + } + + public static final String dateTime(final String format, final Date date) + { + return new SimpleDateFormat(format).format(date); + } + + public static final Date dateTime(final String format, final String ts) + { + try + { + return new SimpleDateFormat(format).parse(ts); + } + catch (ParseException e) + { + throw new RuntimeException(e); + } + } + +} diff --git a/src/main/java/com/ruoyi/framework/config/DruidDBConfig.java b/src/main/java/com/ruoyi/framework/config/DruidDBConfig.java index 3f8185a50..24d07497a 100644 --- a/src/main/java/com/ruoyi/framework/config/DruidDBConfig.java +++ b/src/main/java/com/ruoyi/framework/config/DruidDBConfig.java @@ -23,6 +23,7 @@ import java.sql.SQLException; public class DruidDBConfig { private Logger logger = LoggerFactory.getLogger(DruidDBConfig.class); + @Value("${spring.datasource.url}") private String dbUrl; diff --git a/src/main/java/com/ruoyi/framework/config/I18nConfig.java b/src/main/java/com/ruoyi/framework/config/I18nConfig.java index 48260d326..94ce52f8b 100644 --- a/src/main/java/com/ruoyi/framework/config/I18nConfig.java +++ b/src/main/java/com/ruoyi/framework/config/I18nConfig.java @@ -4,6 +4,7 @@ import java.util.Locale; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.stereotype.Component; import org.springframework.web.servlet.LocaleResolver; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; @@ -16,6 +17,7 @@ import org.springframework.web.servlet.i18n.SessionLocaleResolver; * @author yangzz */ @Configuration +@Component public class I18nConfig extends WebMvcConfigurerAdapter { diff --git a/src/main/java/com/ruoyi/framework/config/ShiroConfig.java b/src/main/java/com/ruoyi/framework/config/ShiroConfig.java index 4e8e0e5e3..62b378118 100644 --- a/src/main/java/com/ruoyi/framework/config/ShiroConfig.java +++ b/src/main/java/com/ruoyi/framework/config/ShiroConfig.java @@ -2,29 +2,27 @@ package com.ruoyi.framework.config; import java.util.LinkedHashMap; import java.util.Map; - import javax.servlet.Filter; - import org.apache.shiro.cache.ehcache.EhCacheManager; import org.apache.shiro.mgt.SecurityManager; -import org.apache.shiro.session.mgt.SessionFactory; import org.apache.shiro.spring.LifecycleBeanPostProcessor; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.filter.authc.LogoutFilter; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; -import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; - import com.ruoyi.common.utils.spring.SpringUtils; import com.ruoyi.project.shiro.realm.UserRealm; import com.ruoyi.project.shiro.session.OnlineSessionDAO; 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.shiro.web.filter.online.OnlineSessionFilter; +import com.ruoyi.project.shiro.web.filter.sync.SyncOnlineSessionFilter; +import com.ruoyi.project.shiro.web.session.OnlineWebSessionManager; +import com.ruoyi.project.shiro.web.session.SpringSessionValidationScheduler; import com.ruoyi.project.system.menu.service.MenuServiceImpl; import at.pollux.thymeleaf.shiro.dialect.ShiroDialect; @@ -39,6 +37,18 @@ public class ShiroConfig { public static final String PREMISSION_STRING = "perms[\"{0}\"]"; + // Session超时时间,单位为毫秒(默认30分钟) + @Value("${shiro.session.expireTime}") + private String expireTime; + + // 同步session到数据库的周期 单位为毫秒(默认1分钟) + @Value("${shiro.session.dbSyncPeriod}") + private String dbSyncPeriod; + + // 相隔多久检查一次session的有效性,单位毫秒,默认就是30分钟 + @Value("${shiro.session.validationInterval}") + private String validationInterval; + /** * 缓存管理器 使用Ehcache实现 */ @@ -75,24 +85,68 @@ public class ShiroConfig * 自定义sessionFactory会话 */ @Bean - SessionFactory sessionFactory() + OnlineSessionFactory sessionFactory() { OnlineSessionFactory sessionFactory = new OnlineSessionFactory(); return sessionFactory; } + /** + * 自定义sessionFactory调度器 + */ + @Bean + SpringSessionValidationScheduler sessionValidationScheduler() + { + SpringSessionValidationScheduler sessionValidationScheduler = new SpringSessionValidationScheduler(); + // 相隔多久检查一次session的有效性,单位毫秒,默认就是60分钟 + sessionValidationScheduler.setSessionValidationInterval(60 * 60 * 1000); + // 设置会话验证调度器进行会话验证时的会话管理器 + sessionValidationScheduler.setSessionManager(sessionValidationManager()); + return sessionValidationScheduler; + } + /** * 会话管理器 */ @Bean - public DefaultWebSessionManager configWebSessionManager() + public OnlineWebSessionManager sessionValidationManager() { - DefaultWebSessionManager manager = new DefaultWebSessionManager(); - manager.setCacheManager(getEhCacheManager());// 加入缓存管理器 - manager.setSessionDAO(sessionDAO());// 设置SessionDao - manager.setDeleteInvalidSessions(true);// 删除过期的session - manager.setGlobalSessionTimeout(sessionDAO().getExpireTime());// 设置全局session超时时间 - manager.setSessionValidationSchedulerEnabled(true);// 是否定时检查session + OnlineWebSessionManager manager = new OnlineWebSessionManager(); + // 加入缓存管理器 + manager.setCacheManager(getEhCacheManager()); + // 删除过期的session + manager.setDeleteInvalidSessions(true); + // 设置全局session超时时间 + manager.setGlobalSessionTimeout(sessionDAO().getExpireTime()); + // 是否定时检查session + manager.setSessionValidationSchedulerEnabled(true); + // 自定义SessionDao + manager.setSessionDAO(sessionDAO()); + // 自定义sessionFactory + manager.setSessionFactory(sessionFactory()); + return manager; + } + + /** + * 会话管理器 + */ + @Bean + public OnlineWebSessionManager sessionManager() + { + OnlineWebSessionManager manager = new OnlineWebSessionManager(); + // 加入缓存管理器 + manager.setCacheManager(getEhCacheManager()); + // 删除过期的session + manager.setDeleteInvalidSessions(true); + // 设置全局session超时时间 + manager.setGlobalSessionTimeout(sessionDAO().getExpireTime()); + // 定义要使用的无效的Session定时调度器 + manager.setSessionValidationScheduler(sessionValidationScheduler()); + // 是否定时检查session + manager.setSessionValidationSchedulerEnabled(true); + // 自定义SessionDao + manager.setSessionDAO(sessionDAO()); + // 自定义sessionFactory manager.setSessionFactory(sessionFactory()); return manager; } @@ -109,7 +163,7 @@ public class ShiroConfig // 注入缓存管理器; securityManager.setCacheManager(getEhCacheManager()); // session管理器 - securityManager.setSessionManager(configWebSessionManager()); + securityManager.setSessionManager(sessionManager()); return securityManager; } diff --git a/src/main/java/com/ruoyi/project/shiro/ShiroConstants.java b/src/main/java/com/ruoyi/project/shiro/ShiroConstants.java index 0c255bab4..c4465789e 100644 --- a/src/main/java/com/ruoyi/project/shiro/ShiroConstants.java +++ b/src/main/java/com/ruoyi/project/shiro/ShiroConstants.java @@ -47,8 +47,4 @@ public interface ShiroConstants */ public String ONLINE_SESSION = "online_session"; - /** - * 仅清空本地缓存 不情况数据库的 - */ - public String ONLY_CLEAR_CACHE = "online_session_only_clear_cache"; } diff --git a/src/main/java/com/ruoyi/project/shiro/session/OnlineSessionDAO.java b/src/main/java/com/ruoyi/project/shiro/session/OnlineSessionDAO.java index 6c99c5879..81180212e 100644 --- a/src/main/java/com/ruoyi/project/shiro/session/OnlineSessionDAO.java +++ b/src/main/java/com/ruoyi/project/shiro/session/OnlineSessionDAO.java @@ -14,13 +14,15 @@ import com.ruoyi.project.util.HttpContextUtils; /** * 针对自定义的ShiroSession的db操作 + * + * @author yangzz */ public class OnlineSessionDAO extends EnterpriseCacheSessionDAO { /** - * Session超时时间,单位为毫秒(默认3分钟) + * Session超时时间,单位为毫秒(默认30分钟) */ - private long expireTime = 3 * 60 * 1000; + private long expireTime = 30 * 60 * 1000; /** * 同步session到数据库的周期 单位为毫秒(默认1分钟) @@ -58,14 +60,7 @@ public class OnlineSessionDAO extends EnterpriseCacheSessionDAO @Override protected Session doReadSession(Serializable sessionId) { - HttpServletRequest request = HttpContextUtils.getHttpServletRequest(); - String uri = request.getServletPath(); - // 如果是静态文件,则不更新SESSION - if (checkStaticLink(uri)) - { - return null; - } - System.out.println("==============doReadSession url=================" + uri); + System.out.println("==============doReadSession url================="); UserOnline userOnline = onlineService.selectByOnlineId(String.valueOf(sessionId)); if (userOnline == null) { @@ -156,6 +151,11 @@ public class OnlineSessionDAO extends EnterpriseCacheSessionDAO { linkFlag = true; } + // 如果是登录请求,则不更新SESSION + if (StringUtils.endsWithAny(uri, new String[] { "/" })) + { + linkFlag = true; + } // 如果是静态文件,则不更新SESSION if (StringUtils.startsWith(uri, "/css") && StringUtils.endsWith(uri, ".css") || StringUtils.startsWith(uri, "/js") && StringUtils.endsWith(uri, ".js") diff --git a/src/main/java/com/ruoyi/project/shiro/session/OnlineSessionFactory.java b/src/main/java/com/ruoyi/project/shiro/session/OnlineSessionFactory.java index 478f3aa48..5eca126de 100644 --- a/src/main/java/com/ruoyi/project/shiro/session/OnlineSessionFactory.java +++ b/src/main/java/com/ruoyi/project/shiro/session/OnlineSessionFactory.java @@ -6,17 +6,29 @@ import org.apache.shiro.session.mgt.SessionContext; import org.apache.shiro.session.mgt.SessionFactory; import org.apache.shiro.web.session.mgt.WebSessionContext; import org.springframework.stereotype.Component; + +import com.ruoyi.common.tools.StringTools; import com.ruoyi.project.shiro.common.utils.IpUtils; import com.ruoyi.project.system.online.domain.OnlineSession; import com.ruoyi.project.system.online.domain.UserOnline; import com.ruoyi.project.util.HttpContextUtils; import eu.bitwalker.useragentutils.UserAgent; +/** + * 自定义sessionFactory会话 + * + * @author yangzz + */ @Component public class OnlineSessionFactory implements SessionFactory { public Session createSession(UserOnline userOnline) { + OnlineSession onlineSession = userOnline.getSession(); + if (StringTools.isNotNull(onlineSession) && onlineSession.getId() == null) + { + onlineSession.setId(userOnline.getSessionId()); + } return userOnline.getSession(); } @@ -30,7 +42,8 @@ public class OnlineSessionFactory implements SessionFactory HttpServletRequest request = (HttpServletRequest) sessionContext.getServletRequest(); if (request != null) { - UserAgent userAgent = UserAgent.parseUserAgentString(HttpContextUtils.getHttpServletRequest().getHeader("User-Agent")); + UserAgent userAgent = UserAgent + .parseUserAgentString(HttpContextUtils.getHttpServletRequest().getHeader("User-Agent")); // 获取客户端操作系统 String os = userAgent.getOperatingSystem().getName(); // 获取客户端浏览器 diff --git a/src/main/java/com/ruoyi/project/shiro/web/OnlineSessionFilter.java b/src/main/java/com/ruoyi/project/shiro/web/filter/online/OnlineSessionFilter.java similarity index 95% rename from src/main/java/com/ruoyi/project/shiro/web/OnlineSessionFilter.java rename to src/main/java/com/ruoyi/project/shiro/web/filter/online/OnlineSessionFilter.java index b02ad2f68..b9eff9f56 100644 --- a/src/main/java/com/ruoyi/project/shiro/web/OnlineSessionFilter.java +++ b/src/main/java/com/ruoyi/project/shiro/web/filter/online/OnlineSessionFilter.java @@ -1,4 +1,4 @@ -package com.ruoyi.project.shiro.web; +package com.ruoyi.project.shiro.web.filter.online; import java.io.IOException; import javax.servlet.ServletRequest; @@ -14,6 +14,7 @@ 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 { @@ -86,6 +87,7 @@ public class OnlineSessionFilter extends AccessControlFilter } // 跳转到登录页 + @Override protected void redirectToLogin(ServletRequest request, ServletResponse response) throws IOException { String loginUrl = getForceLogoutUrl(); diff --git a/src/main/java/com/ruoyi/project/shiro/web/sync/SyncOnlineSessionFilter.java b/src/main/java/com/ruoyi/project/shiro/web/filter/sync/SyncOnlineSessionFilter.java similarity index 88% rename from src/main/java/com/ruoyi/project/shiro/web/sync/SyncOnlineSessionFilter.java rename to src/main/java/com/ruoyi/project/shiro/web/filter/sync/SyncOnlineSessionFilter.java index 1f78aa619..cc640c985 100644 --- a/src/main/java/com/ruoyi/project/shiro/web/sync/SyncOnlineSessionFilter.java +++ b/src/main/java/com/ruoyi/project/shiro/web/filter/sync/SyncOnlineSessionFilter.java @@ -1,4 +1,4 @@ -package com.ruoyi.project.shiro.web.sync; +package com.ruoyi.project.shiro.web.filter.sync; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; @@ -8,6 +8,11 @@ import com.ruoyi.project.shiro.ShiroConstants; import com.ruoyi.project.shiro.session.OnlineSessionDAO; import com.ruoyi.project.system.online.domain.OnlineSession; +/** + * 同步Session数据到Db + * + * @author yangzz + */ public class SyncOnlineSessionFilter extends PathMatchingFilter { @Autowired diff --git a/src/main/java/com/ruoyi/project/shiro/web/session/OnlineWebSessionManager.java b/src/main/java/com/ruoyi/project/shiro/web/session/OnlineWebSessionManager.java new file mode 100644 index 000000000..b651cde12 --- /dev/null +++ b/src/main/java/com/ruoyi/project/shiro/web/session/OnlineWebSessionManager.java @@ -0,0 +1,156 @@ +package com.ruoyi.project.shiro.web.session; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.List; +import org.apache.commons.lang3.time.DateUtils; +import org.apache.shiro.session.ExpiredSessionException; +import org.apache.shiro.session.InvalidSessionException; +import org.apache.shiro.session.Session; +import org.apache.shiro.session.mgt.DefaultSessionKey; +import org.apache.shiro.session.mgt.SessionKey; +import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.ruoyi.common.utils.spring.SpringUtils; +import com.ruoyi.project.shiro.ShiroConstants; +import com.ruoyi.project.system.online.domain.OnlineSession; +import com.ruoyi.project.system.online.domain.UserOnline; +import com.ruoyi.project.system.online.service.UserOnlineServiceImpl; + +/** + * 主要是在此如果会话的属性修改了 就标识下其修改了 然后方便 OnlineSessionDao同步 + * + * @author yangzz + */ +public class OnlineWebSessionManager extends DefaultWebSessionManager +{ + + private static final Logger log = LoggerFactory.getLogger(OnlineWebSessionManager.class); + + @Override + public void setAttribute(SessionKey sessionKey, Object attributeKey, Object value) throws InvalidSessionException + { + super.setAttribute(sessionKey, attributeKey, value); + if (value != null && needMarkAttributeChanged(attributeKey)) + { + OnlineSession s = (OnlineSession) doGetSession(sessionKey); + s.markAttributeChanged(); + } + } + + private boolean needMarkAttributeChanged(Object attributeKey) + { + if (attributeKey == null) + { + return false; + } + String attributeKeyStr = attributeKey.toString(); + // 优化 flash属性没必要持久化 + if (attributeKeyStr.startsWith("org.springframework")) + { + return false; + } + if (attributeKeyStr.startsWith("javax.servlet")) + { + return false; + } + if (attributeKeyStr.equals(ShiroConstants.CURRENT_USERNAME)) + { + return false; + } + return true; + } + + @Override + public Object removeAttribute(SessionKey sessionKey, Object attributeKey) throws InvalidSessionException + { + Object removed = super.removeAttribute(sessionKey, attributeKey); + if (removed != null) + { + OnlineSession s = (OnlineSession) doGetSession(sessionKey); + s.markAttributeChanged(); + } + + return removed; + } + + /** + * 验证session是否有效 用于删除过期session + */ + @Override + public void validateSessions() + { + if (log.isInfoEnabled()) + { + log.info("invalidation sessions..."); + } + + int invalidCount = 0; + + int timeout = (int) this.getGlobalSessionTimeout(); + Date expiredDate = DateUtils.addMilliseconds(new Date(), 0 - timeout); + UserOnlineServiceImpl userOnlineService = SpringUtils.getBean(UserOnlineServiceImpl.class); + List userOnlineList = userOnlineService.selectByOnlineExpired(expiredDate); + // 批量过期删除 + List needOfflineIdList = new ArrayList(); + for (UserOnline userOnline : userOnlineList) + { + try + { + SessionKey key = new DefaultSessionKey(userOnline.getSessionId()); + Session session = retrieveSession(key); + if (session != null) + { + throw new InvalidSessionException(); + } + } + catch (InvalidSessionException e) + { + if (log.isDebugEnabled()) + { + boolean expired = (e instanceof ExpiredSessionException); + String msg = "Invalidated session with id [" + userOnline.getSessionId() + "]" + + (expired ? " (expired)" : " (stopped)"); + log.debug(msg); + } + invalidCount++; + needOfflineIdList.add(userOnline.getSessionId()); + } + + } + if (needOfflineIdList.size() > 0) + { + try + { + userOnlineService.batchDeleteByOnline(needOfflineIdList); + } + catch (Exception e) + { + log.error("batch delete db session error.", e); + } + } + + if (log.isInfoEnabled()) + { + String msg = "Finished invalidation session."; + if (invalidCount > 0) + { + msg += " [" + invalidCount + "] sessions were stopped."; + } + else + { + msg += " No sessions were stopped."; + } + log.info(msg); + } + + } + + @Override + protected Collection getActiveSessions() + { + throw new UnsupportedOperationException("getActiveSessions method not supported"); + } +} diff --git a/src/main/java/com/ruoyi/project/shiro/web/session/SpringSessionValidationScheduler.java b/src/main/java/com/ruoyi/project/shiro/web/session/SpringSessionValidationScheduler.java new file mode 100644 index 000000000..1d02649a4 --- /dev/null +++ b/src/main/java/com/ruoyi/project/shiro/web/session/SpringSessionValidationScheduler.java @@ -0,0 +1,143 @@ +package com.ruoyi.project.shiro.web.session; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import org.apache.shiro.session.mgt.DefaultSessionManager; +import org.apache.shiro.session.mgt.SessionValidationScheduler; +import org.apache.shiro.session.mgt.ValidatingSessionManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 自定义任务调度器完成 + * + * @author yangzz + */ +public class SpringSessionValidationScheduler implements SessionValidationScheduler +{ + + public static final long DEFAULT_SESSION_VALIDATION_INTERVAL = DefaultSessionManager.DEFAULT_SESSION_VALIDATION_INTERVAL; + + private static final Logger log = LoggerFactory.getLogger(SpringSessionValidationScheduler.class); + + /** + * 定时器,用于处理超时的挂起请求,也用于连接断开时的重连。 + */ + private final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); + + private volatile boolean enabled = false; + + /** + * The session manager used to validate sessions. + */ + private ValidatingSessionManager sessionManager; + + /** + * The session validation interval in milliseconds. + */ + private long sessionValidationInterval = DEFAULT_SESSION_VALIDATION_INTERVAL; + + /** + * Default constructor. + */ + public SpringSessionValidationScheduler() + { + } + + /** + * Constructor that specifies the session manager that should be used for validating sessions. + * + * @param sessionManager the SessionManager that should be used to validate sessions. + */ + public SpringSessionValidationScheduler(ValidatingSessionManager sessionManager) + { + this.sessionManager = sessionManager; + } + + public void setSessionManager(ValidatingSessionManager sessionManager) + { + this.sessionManager = sessionManager; + } + + @Override + public boolean isEnabled() + { + return this.enabled; + } + + /** + * Specifies how frequently (in milliseconds) this Scheduler will call the + * {@link org.apache.shiro.session.mgt.ValidatingSessionManager#validateSessions() + * ValidatingSessionManager#validateSessions()} method. + * + *

+ * Unless this method is called, the default value is {@link #DEFAULT_SESSION_VALIDATION_INTERVAL}. + * + * @param sessionValidationInterval + */ + public void setSessionValidationInterval(long sessionValidationInterval) + { + this.sessionValidationInterval = sessionValidationInterval; + } + + /** + * Starts session validation by creating a spring PeriodicTrigger. + */ + @Override + public void enableSessionValidation() + { + + enabled = true; + + if (log.isDebugEnabled()) + { + log.debug("Scheduling session validation job using Spring Scheduler with " + + "session validation interval of [" + sessionValidationInterval + "]ms..."); + } + + try + { + executorService.scheduleAtFixedRate(new Runnable() + { + @Override + public void run() + { + if (enabled) + { + sessionManager.validateSessions(); + } + } + }, 1000, sessionValidationInterval, TimeUnit.MILLISECONDS); + + this.enabled = true; + + if (log.isDebugEnabled()) + { + log.debug("Session validation job successfully scheduled with Spring Scheduler."); + } + + } + catch (Exception e) + { + if (log.isErrorEnabled()) + { + log.error( + "Error starting the Spring Scheduler session validation job. Session validation may not occur.", + e); + } + } + } + + @Override + public void disableSessionValidation() + { + if (log.isDebugEnabled()) + { + log.debug("Stopping Spring Scheduler session validation job..."); + } + + this.enabled = false; + } +} diff --git a/src/main/java/com/ruoyi/project/system/logininfor/service/LogininforServiceImpl.java b/src/main/java/com/ruoyi/project/system/logininfor/service/LogininforServiceImpl.java index 989cf5ddd..5389047e4 100644 --- a/src/main/java/com/ruoyi/project/system/logininfor/service/LogininforServiceImpl.java +++ b/src/main/java/com/ruoyi/project/system/logininfor/service/LogininforServiceImpl.java @@ -22,6 +22,7 @@ public class LogininforServiceImpl implements ILogininforService * * @param logininfor 访问日志对象 */ + @Override public void insertLogininfor(Logininfor logininfor) { logininforDao.insertLogininfor(logininfor); diff --git a/src/main/java/com/ruoyi/project/system/online/dao/IUserOnlineDao.java b/src/main/java/com/ruoyi/project/system/online/dao/IUserOnlineDao.java index 20135cb2e..4c739a917 100644 --- a/src/main/java/com/ruoyi/project/system/online/dao/IUserOnlineDao.java +++ b/src/main/java/com/ruoyi/project/system/online/dao/IUserOnlineDao.java @@ -1,7 +1,6 @@ package com.ruoyi.project.system.online.dao; import java.util.List; - import com.ruoyi.project.system.online.domain.UserOnline; public interface IUserOnlineDao @@ -35,4 +34,12 @@ public interface IUserOnlineDao * @param online 会话信息 */ public List selectUserOnlines(); + + + /** + * 查询过期会话集合 + * + * @param lastAccessTime 过期时间 + */ + public List selectByOnlineExpired(String lastAccessTime); } diff --git a/src/main/java/com/ruoyi/project/system/online/dao/UserOnlineDaoImpl.java b/src/main/java/com/ruoyi/project/system/online/dao/UserOnlineDaoImpl.java index f9f1edb3b..8e48be6d0 100644 --- a/src/main/java/com/ruoyi/project/system/online/dao/UserOnlineDaoImpl.java +++ b/src/main/java/com/ruoyi/project/system/online/dao/UserOnlineDaoImpl.java @@ -26,6 +26,7 @@ public class UserOnlineDaoImpl extends DynamicObjectBaseDao implements IUserOnli * @param sessionId 会话ID * @return 在线用户信息 */ + @Override public int deleteByOnlineId(String sessionId) { return this.delete("SystemOnlineMapper.deleteByOnlineId", sessionId); @@ -36,6 +37,7 @@ public class UserOnlineDaoImpl extends DynamicObjectBaseDao implements IUserOnli * * @param online 会话信息 */ + @Override public int saveByOnline(UserOnline online) { return this.save("SystemOnlineMapper.saveByOnline", online); @@ -46,6 +48,7 @@ public class UserOnlineDaoImpl extends DynamicObjectBaseDao implements IUserOnli * * @param online 会话信息 */ + @Override public List selectUserOnlines() { List userOnlineList = null; @@ -59,4 +62,24 @@ public class UserOnlineDaoImpl extends DynamicObjectBaseDao implements IUserOnli } return userOnlineList; } + + /** + * 查询过期会话集合 + * + * @param lastAccessTime 过期时间 + */ + @Override + public List selectByOnlineExpired(String lastAccessTime) + { + List userOnlineList = null; + try + { + userOnlineList = this.findForList("SystemOnlineMapper.selectByOnlineExpired", lastAccessTime); + } + catch (Exception e) + { + e.printStackTrace(); + } + return userOnlineList; + } } diff --git a/src/main/java/com/ruoyi/project/system/online/domain/OnlineSession.java b/src/main/java/com/ruoyi/project/system/online/domain/OnlineSession.java index 34fbf7a7c..897424477 100644 --- a/src/main/java/com/ruoyi/project/system/online/domain/OnlineSession.java +++ b/src/main/java/com/ruoyi/project/system/online/domain/OnlineSession.java @@ -37,11 +37,13 @@ public class OnlineSession extends SimpleSession // 属性是否改变 优化session数据同步 private transient boolean attributeChanged = false; + @Override public String getHost() { return host; } + @Override public void setHost(String host) { this.host = host; diff --git a/src/main/java/com/ruoyi/project/system/online/service/IUserOnlineService.java b/src/main/java/com/ruoyi/project/system/online/service/IUserOnlineService.java index e03943f20..e9922d236 100644 --- a/src/main/java/com/ruoyi/project/system/online/service/IUserOnlineService.java +++ b/src/main/java/com/ruoyi/project/system/online/service/IUserOnlineService.java @@ -1,5 +1,6 @@ package com.ruoyi.project.system.online.service; +import java.util.Date; import java.util.List; import com.ruoyi.project.system.online.domain.UserOnline; @@ -21,6 +22,14 @@ public interface IUserOnlineService */ public void deleteByOnlineId(String sessionId); + /** + * 通过会话序号删除信息 + * + * @param sessions 会话ID集合 + * @return 在线用户信息 + */ + public void batchDeleteByOnline(List sessions); + /** * 保存会话信息 * @@ -41,4 +50,11 @@ public interface IUserOnlineService * @param sessionId 会话ID */ public void forceLogout(String sessionId); + + /** + * 查询会话集合 + * + * @param online 会话信息 + */ + public List selectByOnlineExpired(Date expiredDate); } diff --git a/src/main/java/com/ruoyi/project/system/online/service/UserOnlineServiceImpl.java b/src/main/java/com/ruoyi/project/system/online/service/UserOnlineServiceImpl.java index e8fc3f3c3..16cc8cd23 100644 --- a/src/main/java/com/ruoyi/project/system/online/service/UserOnlineServiceImpl.java +++ b/src/main/java/com/ruoyi/project/system/online/service/UserOnlineServiceImpl.java @@ -1,9 +1,13 @@ package com.ruoyi.project.system.online.service; +import java.util.Date; import java.util.List; + import org.apache.shiro.session.Session; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; + +import com.ruoyi.common.tools.DateTools; import com.ruoyi.project.shiro.session.OnlineSessionDAO; import com.ruoyi.project.system.online.dao.IUserOnlineDao; import com.ruoyi.project.system.online.domain.UserOnline; @@ -23,6 +27,7 @@ public class UserOnlineServiceImpl implements IUserOnlineService * @param sessionId 会话ID * @return 在线用户信息 */ + @Override public UserOnline selectByOnlineId(String sessionId) { return userOnlineDao.selectByOnlineId(sessionId); @@ -34,6 +39,7 @@ public class UserOnlineServiceImpl implements IUserOnlineService * @param sessionId 会话ID * @return 在线用户信息 */ + @Override public void deleteByOnlineId(String sessionId) { UserOnline userOnline = selectByOnlineId(sessionId); @@ -43,11 +49,31 @@ public class UserOnlineServiceImpl implements IUserOnlineService } } + /** + * 通过会话序号删除信息 + * + * @param sessions 会话ID集合 + * @return 在线用户信息 + */ + @Override + public void batchDeleteByOnline(List sessions) + { + for (String sessionId : sessions) + { + UserOnline userOnline = selectByOnlineId(sessionId); + if (userOnline != null) + { + userOnlineDao.deleteByOnlineId(sessionId); + } + } + } + /** * 保存会话信息 * * @param online 会话信息 */ + @Override public void saveByOnline(UserOnline online) { userOnlineDao.saveByOnline(online); @@ -58,6 +84,7 @@ public class UserOnlineServiceImpl implements IUserOnlineService * * @param online 会话信息 */ + @Override public List selectUserOnlines() { return userOnlineDao.selectUserOnlines(); @@ -68,6 +95,7 @@ public class UserOnlineServiceImpl implements IUserOnlineService * * @param sessionId 会话ID */ + @Override public void forceLogout(String sessionId) { Session session = onlineSessionDAO.readSession(sessionId); @@ -78,4 +106,16 @@ public class UserOnlineServiceImpl implements IUserOnlineService session.setTimeout(1000); userOnlineDao.deleteByOnlineId(sessionId); } + + /** + * 查询会话集合 + * + * @param online 会话信息 + */ + @Override + public List selectByOnlineExpired(Date expiredDate) + { + String lastAccessTime = DateTools.dateTime("yyyy-MM-dd HH:mm:ss", expiredDate); + return userOnlineDao.selectByOnlineExpired(lastAccessTime); + } } diff --git a/src/main/resources/application-druid.yml b/src/main/resources/application-druid.yml index 3acbe61b4..b82d8fb36 100644 --- a/src/main/resources/application-druid.yml +++ b/src/main/resources/application-druid.yml @@ -1,8 +1,8 @@ -y-admin: +ruoyi: uploadPath: D:/var/uploaded_files/ logging: level: - com.bootdo: debug + com.ruoyi: debug spring: datasource: type: com.alibaba.druid.pool.DruidDataSource diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 01486ecd8..958cb3f3d 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -32,4 +32,13 @@ mybatis: # 配置mapper的扫描,找到所有的mapper.xml映射文件 mapperLocations: classpath:mybatis/**/*Mapper.xml # 加载全局的配置文件 - configLocation: classpath:mybatis/mybatis-config.xml \ No newline at end of file + configLocation: classpath:mybatis/mybatis-config.xml +# Shiro +shiro: + session: + # Session超时时间(默认30分钟) + expireTime: 30 + # 同步session到数据库的周期(默认1分钟) + dbSyncPeriod: 1 + # 相隔多久检查一次session的有效性,默认就是60分钟 + validationInterval: 60 \ No newline at end of file diff --git a/src/main/resources/mybatis/system/SystemOnlineMapper.xml b/src/main/resources/mybatis/system/SystemOnlineMapper.xml index 59ea7e7dc..92dd1d419 100644 --- a/src/main/resources/mybatis/system/SystemOnlineMapper.xml +++ b/src/main/resources/mybatis/system/SystemOnlineMapper.xml @@ -44,5 +44,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + + \ No newline at end of file