Spring-Session+Redis实现session共享 实现统计在线人数和踢除用户下线。

首先添加pom相关依赖
<!--
spring session 依赖 --> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session</artifactId> <version>${spring-session.version}</version> </dependency> <!--spring redis依赖--> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>${spring.data.redis.version}</version> </dependency>
web.xml中配置springSessionRepositoryFilter 
<!--
redis共享session --> <filter> <filter-name>springSessionRepositoryFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSessionRepositoryFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>

创建spring-redis.xml并于spring.xml中引入。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c" xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">

    <!-- redis config start -->
    <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <property name="maxIdle" value="${redis.maxIdle}"/>
        <property name="maxTotal" value="${redis.maxActive}"/>
        <property name="maxWaitMillis" value="${redis.maxWait}"/>
        <property name="testOnBorrow" value="${redis.testOnBorrow}"/>
    </bean>

    <bean id="jedisConnFactory"
          class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
          p:use-pool="true"
          p:hostName="${redis.host}"
          p:port="${redis.port}"
          p:password="${redis.pass}"
          p:database="${redis.dbIndex}"
          p:poolConfig-ref="poolConfig"/>
    <!-- stringRedisTemplate-->
    <bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
        <property name="connectionFactory" ref="jedisConnFactory"/>
    </bean>
    <!-- redis template definition -->
    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
        <property name="connectionFactory" ref="jedisConnFactory"/>
        <!--如果不配置Serializer,那么存储的时候缺省使用String,如果用User类型存储,那么会提示错误User can't cast to String!!  -->
        <property name="keySerializer">
            <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
        </property>
        <property name="valueSerializer">
            <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
        </property>
        <property name="hashKeySerializer">
            <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
        </property>
        <property name="hashValueSerializer">
            <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/>
        </property>
        <!--开启事务  -->
        <property name="enableTransactionSupport" value="true"></property>
    </bean>
    <bean id="JdkSerializationRedisSerializer"
          class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/>

    <bean id="GenericToStringSerializer" class="org.springframework.data.redis.serializer.GenericToStringSerializer"
          c:type="java.lang.String"/>


    <bean id="redisCacheManager" class="org.springframework.data.redis.cache.RedisCacheManager">
        <constructor-arg name="redisOperations" ref="redisTemplate"/>
        <property name="defaultExpiration" value="${redis.expiration}"/>
    </bean>

    <bean id="redisCacheConfig" class="com.aloha.app.core.config.RedisCacheConfig">
        <constructor-arg ref="jedisConnFactory"/>
        <constructor-arg ref="redisTemplate"/>
        <constructor-arg ref="redisCacheManager"/>
    </bean>
    <bean id="SessionListener" class="com.aloha.app.root.listener.SessionListener"/>
    <!-- redis共享程序session -->
    <util:constant static-field="org.springframework.session.data.redis.config.ConfigureRedisAction.NO_OP"/>
    <bean id="redisHttpSessionConfiguration"
          class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
        <property name="maxInactiveIntervalInSeconds" value="1800"/>
        <property name="redisNamespace" value="ianbase"/>
        <property name="configureRedisAction" value="NO_OP"/>
        <property name="httpSessionListeners">
            <list>
                <ref bean="SessionListener" />
            </list>
        </property>
    </bean><!-- redis config end -->
    <!--自定义redis工具类,在需要缓存的地方注入此类  -->
    <bean id="redisUtil" class="com.aloha.app.core.utils.RedisUtil">
        <property name="redisTemplate" ref="redisTemplate"/>
    </bean>
</beans>
在spring.xml中引入redis配置文件 redis.properties 并引入spring-redis.xml (spring.xml在此就不赘言了,老生常谈。)
  <!-- 引入properties属性文件 -->
    <context:property-placeholder location="classpath:jdbc/jdbc.properties
    , classpath:redis/redis.properties"/>
    <!-- 引入spring redis整合文件 -->
    <import resource="classpath:spring/spring-redis.xml"/>
redis.properties 配置
# Redis settings
redis.host=127.0.0.1
redis.port=6379
redis.pass=foobared
redis.dbIndex=0
redis.expiration=3000
redis.maxIdle=300
redis.maxActive=600
redis.maxWait=1000
redis.testOnBorrow=true

如果按照如上配置,则需要配置redis的密码为 foobared (可自行定义)

因为笔者使用的是windows版本,可以通过两种方式来配置redis的密码

1.修改redis目录下 redis.windows.conf 中的 requirepass 配置项

配置完成后通过auth 命令登录

配置springSession监听

sessionListener的实现。

package com.aloha.app.root.listener;

import javax.annotation.Resource;
import javax.servlet.ServletContext;
import javax.servlet.http.*;

import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.aloha.app.authority.constant.AuthorityConstant;
import com.aloha.app.authority.entity.SysUser;
import com.aloha.app.root.util.RedisUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SetOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.util.*;

/**
* com.aloha.app.root.listener
*
* @author zgl
* @name SessionListener
* @description
* @date 2018-04-11 14:10
* <p>
* <p>
* Copyright (c) 2018山东安合信达电子科技有限公司 版权所有
* shandong aloha CO.,LTD. All Rights Reserved.
*/
public class SessionListener implements HttpSessionListener {
/**
* 当前用户
*/
public static final String CURRENT_USER = "CURRENT_USER";
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private StringRedisTemplate stringRedisTemplate;

/**
* session创建
*
* @param event
*/
@Override
public void sessionCreated(HttpSessionEvent event) {
HttpSession session = event.getSession();
// System.out.println(session.getAttribute(AuthorityConstant.VERIFICATION)); null 获取不到
//去redis中根据sessionId获取
Set set = redisTemplate.keys("*" + session.getId() + "*");
ArrayList list = new ArrayList(set);
for (int i = 0; i < list.size(); i++) {
String key = list.get(i).toString();
//判断是否hash类型 并且判断是否是有效的用户session
if (redisTemplate.type(key) == DataType.HASH) {
Map<String, Object> map = redisTemplate.opsForHash().entries(key);
System.out.println(redisTemplate.opsForHash().entries(key));
List<String> mapKeys = new ArrayList<String>(map.keySet());
for (int j = 0; j < mapKeys.size(); j++) {
System.out.println(mapKeys.get(j) + ": " + map.get(mapKeys.get(j)));
if (mapKeys.get(j).indexOf(AuthorityConstant.VERIFICATION) != -1) {
System.out.println("验证码产生的session不加用户数");
break;
} else if (mapKeys.get(j).indexOf(AuthorityConstant.SESSION_USER_CODE) != -1 && mapKeys.get(j).indexOf("userId") ==-1 ) {
System.out.println(mapKeys.get(j)+"--------------------------------");
System.out.println("用户登录后产生的session增加用户数");
//存储用户hash
saveUserOnlineHash(session.getId().toString(),(SysUser) map.get(mapKeys.get(j)));
break;
}
}
}
}

// logger.info("创建了一个Session连接:[" + session.getId() + "]");
System.out.println("创建了一个Session连接:[" + session.getId() + "]");

}

/**
* session销毁
*
* @param event
*/
@Override
public void sessionDestroyed(HttpSessionEvent event) {
boolean flag = true;
HttpSession session = event.getSession();
Set<String> keys = redisTemplate.keys("*" + session.getId() + "*");
ArrayList removelist = new ArrayList(keys);
for (int i = 0; i < removelist.size(); i++) {
String key = removelist.get(i).toString();
//判断是否hash类型 并且判断是否是有效的用户session
if (redisTemplate.type(key) == DataType.HASH) {
Map<String, Object> map = redisTemplate.opsForHash().entries(key);
//System.out.println(redisTemplate.opsForHash().entries(key));
List<String> mapKeys = new ArrayList<String>(map.keySet());
for (int j = 0; j < mapKeys.size(); j++) {
//System.out.println(mapKeys.get(j) + ": " + map.get(mapKeys.get(j)));
if (mapKeys.get(j).indexOf(AuthorityConstant.VERIFICATION) != -1) {
System.out.println("验证码产生的session不减用户数");
flag = false;
break;
}
}
}
}
redisTemplate.delete(keys);
Set set = redisTemplate.keys("spring:session:ianbase:expirations*");
ArrayList list = new ArrayList(set);
for (int i = 0; i < list.size(); i++) {
String key = String.valueOf(list.get(i));
//删除set
if (redisTemplate.type(key) == DataType.SET) {
SetOperations<String, String> vo = redisTemplate.opsForSet();
Iterator<String> it = vo.members(key).iterator();
while (it.hasNext()) {
if (it.next().indexOf(session.getId().toString()) != -1) {
System.out.println(key+"................");
redisTemplate.delete(key);
//System.out.println(session.getId().toString());
System.out.println("删除成功了");
}
}
}
}
System.out.println("销毁了一个Session连接:[" + session.getId() + "]");
if (getAllUserNumber() > 0 && flag) {
removeOnlineHashBySessionId(session.getId().toString());
}
}

/**
* 保存用户数
*
* @param n
*/
private void setAllUserNumber(int n) {
Long number = getAllUserNumber() + n;
if (number >= 0) {
// logger.info("用户数:" + number);
System.out.println("用户数:" + number);
stringRedisTemplate.opsForValue().set(AuthorityConstant.ALLUSER_NUMBER, String.valueOf(number));
}
}

/**
* 获取登录用户数目
*
* @return
*/
public Long getAllUserNumber() {
Object v = stringRedisTemplate.opsForValue().get(AuthorityConstant.ALLUSER_NUMBER);
if (v != null) {
return Long.valueOf(v.toString());
}
return 0L;
}
public void saveUserOnlineHash(String sessionId,SysUser sysUser){
Map<String,SysUser> onlineUsers = null;
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
if(redisTemplate.opsForHash().entries(AuthorityConstant.ONLINE_USERS)!=null){
onlineUsers = redisTemplate.opsForHash().entries(AuthorityConstant.ONLINE_USERS);
if(!onlineUsers.keySet().contains(sessionId)){
setAllUserNumber(+1);
}
onlineUsers.put(sessionId,sysUser);
redisTemplate.opsForHash().putAll(AuthorityConstant.ONLINE_USERS,onlineUsers);
}else{
onlineUsers = new HashMap<>();
if(!onlineUsers.keySet().contains(sessionId)){
setAllUserNumber(+1);
}
onlineUsers.put(sessionId,sysUser);
redisTemplate.opsForHash().putAll(AuthorityConstant.ONLINE_USERS,onlineUsers);
}

}
public void removeOnlineHashBySessionId(String sesssionId){
if(redisTemplate.opsForHash().entries(AuthorityConstant.ONLINE_USERS)!=null){
Map<String,SysUser> onlineUsers = redisTemplate.opsForHash().entries(AuthorityConstant.ONLINE_USERS);
if(onlineUsers.keySet().contains(sesssionId)){
setAllUserNumber(-1);
}
onlineUsers.remove(sesssionId);
redisTemplate.delete(AuthorityConstant.ONLINE_USERS);
redisTemplate.opsForHash().putAll(AuthorityConstant.ONLINE_USERS,onlineUsers);
}
}

}

 

 
/**
     * session销毁
     *
     * @param event
     */
    @Override
    public void sessionDestroyed(HttpSessionEvent event) {
        boolean flag = true;
        HttpSession session = event.getSession();
        Set<String> keys = redisTemplate.keys("*" + session.getId() + "*");
        ArrayList removelist = new ArrayList(keys);
        for (int i = 0; i < removelist.size(); i++) {
            String key = removelist.get(i).toString();
            //判断是否hash类型 并且判断是否是有效的用户session
            if (redisTemplate.type(key) == DataType.HASH) {
                Map<String, Object> map = redisTemplate.opsForHash().entries(key);
                //System.out.println(redisTemplate.opsForHash().entries(key));
                List<String> mapKeys = new ArrayList<String>(map.keySet());
                for (int j = 0; j < mapKeys.size(); j++) {
                    //System.out.println(mapKeys.get(j) + ":    " + map.get(mapKeys.get(j)));
                    if (mapKeys.get(j).indexOf(AuthorityConstant.VERIFICATION) != -1) {
                        System.out.println("验证码产生的session不减用户数");
                        flag = false;
                        break;
                    }
                }
            }
        }
        redisTemplate.delete(keys);
        Set set = redisTemplate.keys("spring:session:ianbase:expirations*");
        ArrayList list = new ArrayList(set);
        for (int i = 0; i < list.size(); i++) {
            String key = String.valueOf(list.get(i));
            //删除set
            if (redisTemplate.type(key) == DataType.SET) {
                SetOperations<String, String> vo = redisTemplate.opsForSet();
                Iterator<String> it = vo.members(key).iterator();
                while (it.hasNext()) {
                    if (it.next().indexOf(session.getId().toString()) != -1) {
                        System.out.println(key+"................");
                        redisTemplate.delete(key);
                        //System.out.println(session.getId().toString());
                        System.out.println("删除成功了");
                    }
                }
            }
        }
        System.out.println("销毁了一个Session连接:[" + session.getId() + "]");
        if (getAllUserNumber() > 0 && flag) {
            removeOnlineHashBySessionId(session.getId().toString());
            setAllUserNumber(-1);
        }
    }

    /**
     * 保存用户数
     *
     * @param n
     */
    private void setAllUserNumber(int n) {
        Long number = getAllUserNumber() + n;
        if (number >= 0) {
//            logger.info("用户数:" + number);
            System.out.println("用户数:" + number);
            stringRedisTemplate.opsForValue().set(AuthorityConstant.ALLUSER_NUMBER, String.valueOf(number));
        }
    }

    /**
     * 获取登录用户数目
     *
     * @return
     */
    public Long getAllUserNumber() {
        Object v = stringRedisTemplate.opsForValue().get(AuthorityConstant.ALLUSER_NUMBER);
        if (v != null) {
            return Long.valueOf(v.toString());
        }
        return 0L;
    }
    public void saveUserOnlineHash(String sessionId,SysUser sysUser){
        Map<String,SysUser> onlineUsers = null;
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        if(redisTemplate.opsForHash().entries(AuthorityConstant.ONLINE_USERS)!=null){
            onlineUsers = redisTemplate.opsForHash().entries(AuthorityConstant.ONLINE_USERS);
            onlineUsers.put(sessionId,sysUser);
            redisTemplate.opsForHash().putAll(AuthorityConstant.ONLINE_USERS,onlineUsers);
        }else{
            onlineUsers = new HashMap<>();
            onlineUsers.put(sessionId,sysUser);
            redisTemplate.opsForHash().putAll(AuthorityConstant.ONLINE_USERS,onlineUsers);
        }
    }
    public void removeOnlineHashBySessionId(String sesssionId){
        if(redisTemplate.opsForHash().entries(AuthorityConstant.ONLINE_USERS)!=null){
            Map<String,SysUser> onlineUsers = redisTemplate.opsForHash().entries(AuthorityConstant.ONLINE_USERS);
            onlineUsers.remove(sesssionId);
            redisTemplate.delete(AuthorityConstant.ONLINE_USERS);
            redisTemplate.opsForHash().putAll(AuthorityConstant.ONLINE_USERS,onlineUsers);
        }
    }

}

 

 
具体的逻辑就不多说了 相信大家都能看懂,比较基础的代码。
重点说一下这里的坑!

程序跑起来以后发现 session的销毁方法没有监听到!!!!
解决方法:
查阅许多博客后发现 redis的 notify-keyspace-events 配置没有打开 貌似是 空间事件监听之类的 具体的没有深究。
然后配置 notify-keyspace-events 为KEA
详细的请参考
https://blog.csdn.net/gqtcgq/article/details/50808729
就此完成了登录用户信息的保存,和在线人数的统计。
查看在线人数和踢除用户下线的接口:
 @GetMapping("onlineUser")
    public Result onlineUserCount(HttpSession session) {
        List<JSONObject> users = new ArrayList<>();
        String count = stringRedisTemplate.opsForValue().get(AuthorityConstant.ALLUSER_NUMBER);
        Map<String, Object> map = redisTemplate.opsForHash().entries(AuthorityConstant.ONLINE_USERS);
        List<String> mapKeys = new ArrayList<String>(map.keySet());
        for (int j = 0; j < mapKeys.size(); j++) {
           // System.out.println(mapKeys.get(j) + ":    " + session.getId().toString());
            JSONObject jsonObject = JSONUtil.parseObj(map.get(mapKeys.get(j)));
            if (mapKeys.get(j).equals(session.getId().toString())) {
                jsonObject.put("iscurr", true);
            }
            jsonObject.put("sessionId", mapKeys.get(j));
            users.add(jsonObject);
        }
        return Result.ok().put("data", users).put("count", StringUtils.isEmpty(count) ? "0" : count);
    }

    @PostMapping("downline")
    public Result downline(@RequestBody String sid) {
       sid= sid.replaceAll("=","");
        Map<String, SysUser> map = redisTemplate.opsForHash().entries(AuthorityConstant.ONLINE_USERS);
        Set<String> keys = redisTemplate.keys("*" + sid + "*");
        redisTemplate.delete(keys);
        Set set = redisTemplate.keys("spring:session:ianbase:expirations*");
        ArrayList list = new ArrayList(set);
        for (int i = 0; i < list.size(); i++) {
            String key = String.valueOf(list.get(i));
            //删除set
            if (redisTemplate.type(key) == DataType.SET) {
                SetOperations<String, String> vo = redisTemplate.opsForSet();
                Iterator<String> it = vo.members(key).iterator();
                while (it.hasNext()) {
                    if (it.next().indexOf(sid.toString()) != -1) {
                        redisTemplate.delete(key);
                        removeOnlineHashBySessionId(sid);
                        //减用户数
                        long count = Long.parseLong(stringRedisTemplate.opsForValue().get(AuthorityConstant.ALLUSER_NUMBER)) - 1;
                        stringRedisTemplate.opsForValue().set(AuthorityConstant.ALLUSER_NUMBER, String.valueOf(count));
                        System.out.println("强制下线成功");
                    }
                }
            }
        }
        return Result.ok("已强制该用户下线。");
    }

    public void removeOnlineHashBySessionId(String sesssionId) {
        if (redisTemplate.opsForHash().entries(AuthorityConstant.ONLINE_USERS) != null) {
            Map<String, SysUser> onlineUsers = redisTemplate.opsForHash().entries(AuthorityConstant.ONLINE_USERS);
            onlineUsers.remove(sesssionId);
            redisTemplate.delete(AuthorityConstant.ONLINE_USERS);
            redisTemplate.opsForHash().putAll(AuthorityConstant.ONLINE_USERS, onlineUsers);
        }
    }

 sid 为前台传过来的对应用户的sessionid

然后这里扩展一点。负载均衡宕机情况下在线用户人数没有对应改变该如何处理呢?

这里简单说一下,笔者通过两种方式去实现了一下。

1.监听spring的生命周期实现redis数据

2.监听tomcat的开启关闭时间。

二者选其一即可 但缺点是 只有调用tomcat 的shutdown脚本时才会触发销毁。

因为此时笔者所做项目对这块没有什么要求 因此选用了第一种实现方式。

见代码如下:

@Component
public class ContextRefreshedListener implements ApplicationListener<ContextRefreshedEvent> {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private RedisTemplate redisTemplate;
    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        // 防止子容器启动完成后再次调用
        if(contextRefreshedEvent.getApplicationContext().getParent() == null){
            //判断当前会否有tomcat在运行 如果有则说明tomcat数 的key已经创建 否则重新创建
            if(stringRedisTemplate.opsForValue().get(AuthorityConstant.TOMCAT_NUM)!=null){
                long count =Long.parseLong(stringRedisTemplate.opsForValue().get(AuthorityConstant.TOMCAT_NUM));
                stringRedisTemplate.opsForValue().set(AuthorityConstant.TOMCAT_NUM,String.valueOf(count+1));
            }else{
                //初始化
                stringRedisTemplate.opsForValue().set(AuthorityConstant.TOMCAT_NUM,"1");
            }
            System.out.println("spring容器初始化");
        }
    }
    @Component
    public class StopAddDataListener implements ApplicationListener<ContextClosedEvent> {
        public void onApplicationEvent(ContextClosedEvent contextClosedEvent) {
            if(contextClosedEvent.getApplicationContext().getParent() == null) {
                if(stringRedisTemplate.opsForValue().get(AuthorityConstant.TOMCAT_NUM)!=null){
                    long count =Long.parseLong(stringRedisTemplate.opsForValue().get(AuthorityConstant.TOMCAT_NUM));
                    if(count-1==0){
                        //清理redis
                        flushDB();
                    }else{
                        stringRedisTemplate.opsForValue().set(AuthorityConstant.TOMCAT_NUM,String.valueOf(count-1));
                    }
                }
               System.out.println("spring容器关闭");
            }
        }
    }
    public void flushDB() {
        redisTemplate.getConnectionFactory().getConnection().flushDb();
    }
}

 好吧,就写到这里吧,希望对有需要的人提供一定的帮助。

 

 

 


 

 

 

SessionListener 的实现
posted @ 2018-06-04 18:48  蒙奇D流氓  阅读(13273)  评论(0编辑  收藏  举报