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 = '在线用户记录';