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 的實現


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM