首先添加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 的實現
