支持服务重启后无感恢复用户会话

springboot2
RuoYi 3 weeks ago
parent 266f935229
commit 189fcd1795

@ -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;

@ -1,4 +1,4 @@
package com.ruoyi.framework.shiro.session;
package com.ruoyi.common.core.session;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;

@ -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);
}
};
}

@ -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);
}
}

@ -1,13 +1,20 @@
package com.ruoyi.framework.shiro.session;
import java.io.ByteArrayInputStream;
import java.io.ObjectInputStream;
import javax.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 com.ruoyi.system.domain.SysUserOnline;
/**
* sessionFactory
@ -17,6 +24,40 @@ import com.ruoyi.common.utils.http.UserAgentUtils;
@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)
{

@ -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;
/**

@ -4,7 +4,7 @@ import javax.servlet.ServletRequest;
import javax.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;
/**

@ -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;

@ -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())

@ -16,10 +16,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="startTimestamp" column="start_timestamp" />
<result property="lastAccessTime" column="last_access_time" />
<result property="expireTime" column="expire_time" />
<result property="sessionData" column="session_data" />
</resultMap>
<sql id="selectOnlineVo">
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
</sql>
@ -29,8 +30,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</select>
<insert id="saveOnline" parameterType="SysUserOnline">
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})
</insert>
<delete id="deleteOnlineById" parameterType="String">

@ -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 = '在线用户记录';
@ -664,7 +665,21 @@ insert into sys_notice values('3', '若依开源框架介绍', '1', '<p><span st
-- ----------------------------
-- 19、代码生成业务表
-- 19、公告已读记录表
-- ----------------------------
drop table if exists sys_notice_read;
create table sys_notice_read (
read_id bigint(20) not null auto_increment comment '已读主键',
notice_id int(4) not null comment '公告id',
user_id bigint(20) not null comment '用户id',
read_time datetime not null comment '阅读时间',
primary key (read_id),
unique key uk_user_notice (user_id, notice_id) comment '同一用户同一公告只记录一次'
) engine=innodb auto_increment=1 comment='公告已读记录表';
-- ----------------------------
-- 20、代码生成业务表
-- ----------------------------
drop table if exists gen_table;
create table gen_table (
@ -694,7 +709,7 @@ create table gen_table (
-- ----------------------------
-- 20、代码生成业务表字段
-- 21、代码生成业务表字段
-- ----------------------------
drop table if exists gen_table_column;
create table gen_table_column (
Loading…
Cancel
Save