springcloud+redis+session实现单IP登录
本文介绍使用redis+session在springcloud框架下实现以下功能:
1.跨域共享session(使用redis+session)
2.历史访问量和实时访问量统计(使用HttpSessionListener)
3.单点登录(使用redis+session)
如何建立Springcloud工程在此不多加赘述。
Step1:跨域共享Session
由于SpringCloud为多模块项目,在前后端分离的情况下难免会有跨域请求接口的情况,如果代码中使用了session缓存数据,在跨域请求时会出现SessionID不一致,这样就获取不到想要的数据。使用SpringSessionRedis可以解决这一问题,实现跨域访问。步骤比较简单:
1.加入依赖
<!-- redis + session -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
2.配置yml文件
redis:
database: 0
host: localhost
port: 6379
password:
timeout: 20000
jedis:
pool:
max-active: 8
max-wait: -1
max-idle: 8
min-idle: 0
session:
store-type: redis
3.编写配置类
@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 300) //配置过期时间
public class RedisSessionConfig {
}
这样在不同域中便可以实现Session共享了。
Step2:Web历史访问量和实时访问量统计
这一步使用监听器对HttpSession实现监听,在监听到有Session创建或过期时进行记录。使用Redis存放历史访问量。
首先要先知道ServletContext和Servlet的关系。一个web应用对应一个ServletContext实例,这个实例是应用部署启动后,servlet容器为应用创建的。ServletContext实例包含了所有servlet共享的资源信息。通过提供一组方法给servlet使用,用来和servlet容器通讯,比如获取文件的MIME类型、分发请求、记录日志等。Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。
1.首先我们创建一个ServletContextListener
@Component
public class ServletContextListener implements javax.servlet.ServletContextListener {
private Logger logger=LoggerFactory.getLogger(this.getClass());
private Map<String, HttpSession> userMap = new HashMap<>();
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
logger.info("===========usermap初始化===========");
servletContextEvent.getServletContext().setAttribute("userMap",userMap);
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
}
}
这部分实现了在程序初始化时创建一个用于存放在线人数的HashMap,并将这个HashMap存放到ServletContext中。
2.创建HttpSessionListener
@Component
public class HttpSessionListener implements javax.servlet.http.HttpSessionListener {
@Autowired
RedisUtil redisUtil;
@Autowired
private StringRedisTemplate redisTemplate;
private Logger logger=LoggerFactory.getLogger(this.getClass());
/**
*@description session创建监听
*@author lugton
*@date 2019/10/19
*@return
*/
@Override
public void sessionCreated(HttpSessionEvent httpSessionEvent) {
HttpSession session=httpSessionEvent.getSession();
ServletContext servletContext=session.getServletContext();
Map<String, HttpSession> userMap= (Map<String, HttpSession>) servletContext.getAttribute("userMap");
try {
String value=redisUtil.get("TotalCount",0);
Integer count= Integer.parseInt(value);
//判断userMap中是否存在该Session,若不存在,则访客+1
if (!userMap.containsKey(session.getId())){
userMap.put(session.getId(),session);//新访客存入userMap
logger.info("============新访客进入===========");
count=count+1;//有新访客 历史访问+1
redisUtil.set("TotalCount",(count).toString(),0);//历史访问量存入redis
}
}catch (Exception e){
e.printStackTrace();
}
servletContext.setAttribute("userMap",userMap);
}
/**
*@description session过期监听
*@author lugton
*@date 2019/10/19
*@return
*/
@Override
public void sessionDestroyed(HttpSessionEvent se) {
//获取session
HttpSession session = se.getSession();
ServletContext servletContext=session.getServletContext();
Map<String, HttpSession> userMap= (Map<String, HttpSession>) servletContext.getAttribute("userMap");
//判断当前过期的Session是否在userMap中
if (userMap.containsKey(session.getId())){
userMap.remove(session.getId());//移除
logger.info("==============访客离开=============");
}
servletContext.setAttribute("userMap",userMap);
}
}
3.redisUtil的代码如下
这部分本打算用JedisPool但总是获取不到连接池的资源,故弃之,各位有好的建议欢迎留言指出。
@Component
public class RedisUtil {
private Logger logger=LoggerFactory.getLogger(this.getClass());
// @Autowired
// JedisPool jedisPool;
/**
*@description 根据数据库和key获取value
*@author lugton
*@date 2019/10/19
*@return
*/
public String get(String key,int indexdb) {
Jedis jedis = null;
String value = null;
try {
jedis = new Jedis("localhost",6379);
jedis.select(indexdb);
value = jedis.get(key);
logger.info("历史访客数量为:"+value);
} catch (Exception e) {
logger.error(e.getMessage());
}finally {
// jedisPool.returnResource(jedis);
jedis.close();
}
return value;
}
/**
*@description 存入数据
*@author lugton
*@date 2019/10/19
*@return
*/
public String set(String key, String value,int indexdb) {
Jedis jedis = null;
try {
// jedis = jedisPool.getResource();
jedis=new Jedis("localhost",6379);
jedis.select(indexdb);
return jedis.set(key, value);
} catch (Exception e) {
logger.error(e.getMessage());
return "0";
}finally {
jedis.close();
}
}
}
这样,就可以在redis中获得历史访问量,在userMap.size中获得实时用户量。
Step3:实现单IP登陆
在用redis共享session时,就想到了可以用redis存放当前在线用户,实现单IP登陆,思路比较简单,如果有不足之处欢迎各位在评论中指出。
1.登陆控制层
/**
*@description 登陆
*@author lugton
*@date 2019/10/16
*@return
*/
@RequestMapping("/login")
public Map<String,Object> Login(@RequestParam("username") String userName, @RequestParam("password") String password, HttpServletRequest request, HttpServletResponse response){
Map<String, Object> map = new HashMap<>();
User user = userDao.findUserByUserName(userName);
//判断该用户是否已存在redis中
String loginSessionId=redisTemplate.opsForValue().get(user.getUserName());
//加密为md5
// password= Md5Util.StringInMd5(password);
if (user!=null){
//如果该用户在redis中存在,判断其sessionID和当前Session是否相同,
//如果不同,则提示错误
if (loginSessionId!=null&&(!loginSessionId.equals(request.getSession().getId()))){
map.put("message", "该用户已登陆");
map.put("result", false);
}else {
if (password.equals(user.getPassWord())){
request.getSession().setAttribute("user", user);
//将该用户存入redis中
redisTemplate.opsForValue().set(user.getUserName(),request.getSession().getId());
map.put("message", "登陆成功");
map.put("result", true);
}else {
map.put("message", "密码错误!");
map.put("result", false);
}
}
}else {
map.put("message", "该用户不存在!");
map.put("result", false);
}
logger.info(String.valueOf(map.get("message")));
return map;
}
2.Session过期时,将Redis中存放的对应用户清空
@Override
public void sessionDestroyed(HttpSessionEvent se) {
/////////////////////////////////
//重复部分在此省略
User user= (User) session.getAttribute("user");
if (user!=null){
redisTemplate.opsForValue().set(user.getUserName(),"",1,TimeUnit.SECONDS);
}
}
这样就实现了单点登陆的功能。经测试此功能能够实现,因为是自己想的方法,难免有不足之处,欢迎大家指出!