From 589e290e6c1cf142271b7a46c302bb2bc980077d Mon Sep 17 00:00:00 2001 From: RuoYi Date: Thu, 19 Mar 2026 18:40:30 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8C=81=E6=9C=8D=E5=8A=A1=E9=87=8D=E5=90=AF?= =?UTF-8?q?=E5=90=8E=E6=97=A0=E6=84=9F=E6=81=A2=E5=A4=8D=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E4=BC=9A=E8=AF=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../monitor/SysUserOnlineController.java | 2 +- .../common/core}/session/OnlineSession.java | 330 +++++++++--------- .../manager/factory/AsyncFactory.java | 19 +- .../shiro/service/SysShiroService.java | 21 +- .../shiro/session/OnlineSessionDAO.java | 1 + .../shiro/session/OnlineSessionFactory.java | 43 ++- .../filter/online/OnlineSessionFilter.java | 2 +- .../filter/sync/SyncOnlineSessionFilter.java | 2 +- .../web/session/OnlineWebSessionManager.java | 2 +- .../ruoyi/system/domain/SysUserOnline.java | 31 +- .../mapper/system/SysUserOnlineMapper.xml | 9 +- sql/ry_20260318.sql | 1 + 12 files changed, 270 insertions(+), 193 deletions(-) rename {ruoyi-framework/src/main/java/com/ruoyi/framework/shiro => ruoyi-common/src/main/java/com/ruoyi/common/core}/session/OnlineSession.java (93%) diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysUserOnlineController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysUserOnlineController.java index 3ae9e81ea..fa3314f2e 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysUserOnlineController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysUserOnlineController.java @@ -13,11 +13,11 @@ import com.ruoyi.common.annotation.Log; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.core.session.OnlineSession; import com.ruoyi.common.core.text.Convert; import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.enums.OnlineStatus; import com.ruoyi.common.utils.ShiroUtils; -import com.ruoyi.framework.shiro.session.OnlineSession; import com.ruoyi.framework.shiro.session.OnlineSessionDAO; import com.ruoyi.system.domain.SysUserOnline; import com.ruoyi.system.service.ISysUserOnlineService; diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/session/OnlineSession.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/session/OnlineSession.java similarity index 93% rename from ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/session/OnlineSession.java rename to ruoyi-common/src/main/java/com/ruoyi/common/core/session/OnlineSession.java index 95d9b4c7e..4e32311f8 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/session/OnlineSession.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/session/OnlineSession.java @@ -1,165 +1,165 @@ -package com.ruoyi.framework.shiro.session; - -import org.apache.commons.lang3.builder.ToStringBuilder; -import org.apache.commons.lang3.builder.ToStringStyle; -import org.apache.shiro.session.mgt.SimpleSession; -import com.ruoyi.common.enums.OnlineStatus; - -/** - * 在线用户会话属性 - * - * @author ruoyi - */ -public class OnlineSession extends SimpleSession -{ - private static final long serialVersionUID = 1L; - - /** 用户ID */ - private Long userId; - - /** 用户名称 */ - private String loginName; - - /** 部门名称 */ - private String deptName; - - /** 用户头像 */ - private String avatar; - - /** 登录IP地址 */ - private String host; - - /** 浏览器类型 */ - private String browser; - - /** 操作系统 */ - private String os; - - /** 在线状态 */ - private OnlineStatus status = OnlineStatus.on_line; - - /** 属性是否改变 优化session数据同步 */ - private transient boolean attributeChanged = false; - - @Override - public String getHost() - { - return host; - } - - @Override - public void setHost(String host) - { - this.host = host; - } - - public String getBrowser() - { - return browser; - } - - public void setBrowser(String browser) - { - this.browser = browser; - } - - public String getOs() - { - return os; - } - - public void setOs(String 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 OnlineStatus getStatus() - { - return status; - } - - public void setStatus(OnlineStatus status) - { - this.status = status; - } - - public void markAttributeChanged() - { - this.attributeChanged = true; - } - - public void resetAttributeChanged() - { - this.attributeChanged = false; - } - - public boolean isAttributeChanged() - { - return attributeChanged; - } - - public String getAvatar() { - return avatar; - } - - public void setAvatar(String avatar) { - this.avatar = avatar; - } - - @Override - public void setAttribute(Object key, Object value) - { - super.setAttribute(key, value); - } - - @Override - public Object removeAttribute(Object key) - { - return super.removeAttribute(key); - } - - @Override - public String toString() { - return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) - .append("userId", getUserId()) - .append("loginName", getLoginName()) - .append("deptName", getDeptName()) - .append("avatar", getAvatar()) - .append("host", getHost()) - .append("browser", getBrowser()) - .append("os", getOs()) - .append("status", getStatus()) - .append("attributeChanged", isAttributeChanged()) - .toString(); - } -} +package com.ruoyi.common.core.session; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import org.apache.shiro.session.mgt.SimpleSession; +import com.ruoyi.common.enums.OnlineStatus; + +/** + * 在线用户会话属性 + * + * @author ruoyi + */ +public class OnlineSession extends SimpleSession +{ + private static final long serialVersionUID = 1L; + + /** 用户ID */ + private Long userId; + + /** 用户名称 */ + private String loginName; + + /** 部门名称 */ + private String deptName; + + /** 用户头像 */ + private String avatar; + + /** 登录IP地址 */ + private String host; + + /** 浏览器类型 */ + private String browser; + + /** 操作系统 */ + private String os; + + /** 在线状态 */ + private OnlineStatus status = OnlineStatus.on_line; + + /** 属性是否改变 优化session数据同步 */ + private transient boolean attributeChanged = false; + + @Override + public String getHost() + { + return host; + } + + @Override + public void setHost(String host) + { + this.host = host; + } + + public String getBrowser() + { + return browser; + } + + public void setBrowser(String browser) + { + this.browser = browser; + } + + public String getOs() + { + return os; + } + + public void setOs(String 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 OnlineStatus getStatus() + { + return status; + } + + public void setStatus(OnlineStatus status) + { + this.status = status; + } + + public void markAttributeChanged() + { + this.attributeChanged = true; + } + + public void resetAttributeChanged() + { + this.attributeChanged = false; + } + + public boolean isAttributeChanged() + { + return attributeChanged; + } + + public String getAvatar() { + return avatar; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + @Override + public void setAttribute(Object key, Object value) + { + super.setAttribute(key, value); + } + + @Override + public Object removeAttribute(Object key) + { + return super.removeAttribute(key); + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("userId", getUserId()) + .append("loginName", getLoginName()) + .append("deptName", getDeptName()) + .append("avatar", getAvatar()) + .append("host", getHost()) + .append("browser", getBrowser()) + .append("os", getOs()) + .append("status", getStatus()) + .append("attributeChanged", isAttributeChanged()) + .toString(); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/factory/AsyncFactory.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/factory/AsyncFactory.java index e73af79e1..ac0f8b185 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/factory/AsyncFactory.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/factory/AsyncFactory.java @@ -1,9 +1,12 @@ package com.ruoyi.framework.manager.factory; +import java.io.ByteArrayOutputStream; +import java.io.ObjectOutputStream; import java.util.TimerTask; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.core.session.OnlineSession; import com.ruoyi.common.utils.AddressUtils; import com.ruoyi.common.utils.LogUtils; import com.ruoyi.common.utils.ServletUtils; @@ -11,7 +14,6 @@ import com.ruoyi.common.utils.ShiroUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.http.UserAgentUtils; import com.ruoyi.common.utils.spring.SpringUtils; -import com.ruoyi.framework.shiro.session.OnlineSession; import com.ruoyi.system.domain.SysLogininfor; import com.ruoyi.system.domain.SysOperLog; import com.ruoyi.system.domain.SysUserOnline; @@ -54,8 +56,21 @@ public class AsyncFactory online.setBrowser(session.getBrowser()); online.setOs(session.getOs()); online.setStatus(session.getStatus()); + // 序列化 OnlineSession,重启后可从 DB 恢复会话 + try + { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(bos); + oos.writeObject(session); + oos.close(); + online.setSessionData(bos.toByteArray()); + } + catch (Exception e) + { + // 序列化失败不影响正常流程,仅记录日志 + LoggerFactory.getLogger(AsyncFactory.class).warn("serialize OnlineSession failed", e); + } SpringUtils.getBean(ISysUserOnlineService.class).saveOnline(online); - } }; } diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/service/SysShiroService.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/service/SysShiroService.java index 1fb9c7e46..4d402250b 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/service/SysShiroService.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/service/SysShiroService.java @@ -4,9 +4,10 @@ import java.io.Serializable; import org.apache.shiro.session.Session; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import com.ruoyi.common.core.session.OnlineSession; import com.ruoyi.common.utils.StringUtils; -import com.ruoyi.framework.shiro.session.OnlineSession; import com.ruoyi.system.domain.SysUserOnline; +import com.ruoyi.framework.shiro.session.OnlineSessionFactory; import com.ruoyi.system.service.ISysUserOnlineService; /** @@ -20,6 +21,9 @@ public class SysShiroService @Autowired private ISysUserOnlineService onlineService; + @Autowired + private OnlineSessionFactory onlineSessionFactory; + /** * 删除会话 * @@ -44,19 +48,6 @@ public class SysShiroService public Session createSession(SysUserOnline userOnline) { - OnlineSession onlineSession = new OnlineSession(); - if (StringUtils.isNotNull(userOnline)) - { - onlineSession.setId(userOnline.getSessionId()); - onlineSession.setHost(userOnline.getIpaddr()); - onlineSession.setBrowser(userOnline.getBrowser()); - onlineSession.setOs(userOnline.getOs()); - onlineSession.setDeptName(userOnline.getDeptName()); - onlineSession.setLoginName(userOnline.getLoginName()); - onlineSession.setStartTimestamp(userOnline.getStartTimestamp()); - onlineSession.setLastAccessTime(userOnline.getLastAccessTime()); - onlineSession.setTimeout(userOnline.getExpireTime()); - } - return onlineSession; + return onlineSessionFactory.createSession(userOnline); } } diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/session/OnlineSessionDAO.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/session/OnlineSessionDAO.java index bbcf1e342..db4d5a606 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/session/OnlineSessionDAO.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/session/OnlineSessionDAO.java @@ -7,6 +7,7 @@ import org.apache.shiro.session.UnknownSessionException; import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import com.ruoyi.common.core.session.OnlineSession; import com.ruoyi.common.enums.OnlineStatus; import com.ruoyi.framework.manager.AsyncManager; import com.ruoyi.framework.manager.factory.AsyncFactory; diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/session/OnlineSessionFactory.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/session/OnlineSessionFactory.java index cf243c39f..7f2e65f68 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/session/OnlineSessionFactory.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/session/OnlineSessionFactory.java @@ -1,13 +1,20 @@ package com.ruoyi.framework.shiro.session; +import java.io.ByteArrayInputStream; +import java.io.ObjectInputStream; +import jakarta.servlet.http.HttpServletRequest; import org.apache.shiro.session.Session; import org.apache.shiro.session.mgt.SessionContext; import org.apache.shiro.session.mgt.SessionFactory; import org.apache.shiro.web.session.mgt.WebSessionContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; +import com.ruoyi.common.core.session.OnlineSession; import com.ruoyi.common.utils.IpUtils; +import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.http.UserAgentUtils; -import jakarta.servlet.http.HttpServletRequest; +import com.ruoyi.system.domain.SysUserOnline; /** * 自定义sessionFactory会话 @@ -17,6 +24,40 @@ import jakarta.servlet.http.HttpServletRequest; @Component public class OnlineSessionFactory implements SessionFactory { + private static final Logger log = LoggerFactory.getLogger(OnlineSessionFactory.class); + + public Session createSession(SysUserOnline userOnline) + { + // 优先从序列化数据恢复完整 Session(包含 principals 认证信息) + if (userOnline.getSessionData() != null && userOnline.getSessionData().length > 0) + { + try + { + ByteArrayInputStream bis = new ByteArrayInputStream(userOnline.getSessionData()); + ObjectInputStream ois = new ObjectInputStream(bis); + OnlineSession onlineSession = (OnlineSession) ois.readObject(); + ois.close(); + // 确保 sessionId 和 DB 一致 + if (onlineSession.getId() == null) + { + onlineSession.setId(userOnline.getSessionId()); + } + return onlineSession; + } + catch (Exception e) + { + log.warn("deserialize OnlineSession failed, sessionId={}", userOnline.getSessionId(), e); + } + } + // 降级:仅用基础字段重建(无认证信息,会触发重新登录) + OnlineSession onlineSession = userOnline.getSession(); + if (StringUtils.isNotNull(onlineSession) && onlineSession.getId() == null) + { + onlineSession.setId(userOnline.getSessionId()); + } + return onlineSession; + } + @Override public Session createSession(SessionContext initData) { diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/online/OnlineSessionFilter.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/online/OnlineSessionFilter.java index cd811f737..11d4311a2 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/online/OnlineSessionFilter.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/online/OnlineSessionFilter.java @@ -10,9 +10,9 @@ import org.apache.shiro.web.util.WebUtils; import org.springframework.beans.factory.annotation.Value; import com.ruoyi.common.constant.ShiroConstants; import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.session.OnlineSession; import com.ruoyi.common.enums.OnlineStatus; import com.ruoyi.common.utils.ShiroUtils; -import com.ruoyi.framework.shiro.session.OnlineSession; import com.ruoyi.framework.shiro.session.OnlineSessionDAO; /** diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/sync/SyncOnlineSessionFilter.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/sync/SyncOnlineSessionFilter.java index f0efaa05f..a32c780f5 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/sync/SyncOnlineSessionFilter.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/sync/SyncOnlineSessionFilter.java @@ -4,7 +4,7 @@ import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import org.apache.shiro.web.filter.PathMatchingFilter; import com.ruoyi.common.constant.ShiroConstants; -import com.ruoyi.framework.shiro.session.OnlineSession; +import com.ruoyi.common.core.session.OnlineSession; import com.ruoyi.framework.shiro.session.OnlineSessionDAO; /** diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/session/OnlineWebSessionManager.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/session/OnlineWebSessionManager.java index 75a557d90..07c692994 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/session/OnlineWebSessionManager.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/session/OnlineWebSessionManager.java @@ -14,10 +14,10 @@ import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.ruoyi.common.constant.ShiroConstants; +import com.ruoyi.common.core.session.OnlineSession; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.bean.BeanUtils; import com.ruoyi.common.utils.spring.SpringUtils; -import com.ruoyi.framework.shiro.session.OnlineSession; import com.ruoyi.system.domain.SysUserOnline; import com.ruoyi.system.service.ISysUserOnlineService; diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserOnline.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserOnline.java index d259a5ba0..64dd0a7ba 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserOnline.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserOnline.java @@ -4,6 +4,7 @@ import java.util.Date; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import com.ruoyi.common.core.domain.BaseEntity; +import com.ruoyi.common.core.session.OnlineSession; import com.ruoyi.common.enums.OnlineStatus; /** @@ -48,6 +49,12 @@ public class SysUserOnline extends BaseEntity /** 在线状态 */ private OnlineStatus status = OnlineStatus.on_line; + /** 备份的当前用户会话 */ + private OnlineSession session; + + /** 序列化的Session数据\uff0c用于服务重吏后恢复会话 */ + private byte[] sessionData; + public String getSessionId() { return sessionId; @@ -157,8 +164,28 @@ public class SysUserOnline extends BaseEntity { this.status = status; } - - @Override + + public OnlineSession getSession() + { + return session; + } + + public void setSession(OnlineSession session) + { + this.session = session; + } + + public byte[] getSessionData() + { + return sessionData; + } + + public void setSessionData(byte[] sessionData) + { + this.sessionData = sessionData; + } + + @Override public String toString() { return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) .append("sessionId", getSessionId()) diff --git a/ruoyi-system/src/main/resources/mapper/system/SysUserOnlineMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysUserOnlineMapper.xml index 92f684358..e1e987c2a 100644 --- a/ruoyi-system/src/main/resources/mapper/system/SysUserOnlineMapper.xml +++ b/ruoyi-system/src/main/resources/mapper/system/SysUserOnlineMapper.xml @@ -16,10 +16,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + - + - select sessionId, login_name, dept_name, ipaddr, login_location, browser, os, status, start_timestamp, last_access_time, expire_time + select sessionId, login_name, dept_name, ipaddr, login_location, browser, os, status, start_timestamp, last_access_time, expire_time, session_data from sys_user_online @@ -29,8 +30,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - replace into sys_user_online(sessionId, login_name, dept_name, ipaddr, login_location, browser, os, status, start_timestamp, last_access_time, expire_time) - values (#{sessionId}, #{loginName}, #{deptName}, #{ipaddr}, #{loginLocation}, #{browser}, #{os}, #{status}, #{startTimestamp}, #{lastAccessTime}, #{expireTime}) + replace into sys_user_online(sessionId, login_name, dept_name, ipaddr, login_location, browser, os, status, start_timestamp, last_access_time, expire_time, session_data) + values (#{sessionId}, #{loginName}, #{deptName}, #{ipaddr}, #{loginLocation}, #{browser}, #{os}, #{status}, #{startTimestamp}, #{lastAccessTime}, #{expireTime}, #{sessionData,jdbcType=BLOB,typeHandler=org.apache.ibatis.type.BlobTypeHandler}) diff --git a/sql/ry_20260318.sql b/sql/ry_20260318.sql index 26ff49abb..992afe756 100644 --- a/sql/ry_20260318.sql +++ b/sql/ry_20260318.sql @@ -588,6 +588,7 @@ create table sys_user_online ( start_timestamp datetime comment 'session创建时间', last_access_time datetime comment 'session最后访问时间', expire_time int(5) default 0 comment '超时时间,单位为分钟', + session_data blob default null comment '序列化的Session数据,用于服务重启后恢复会话', primary key (sessionId) ) engine=innodb comment = '在线用户记录';