SpringBoot+Redis(redis集群+池化:一致性Hash分片算法) 实现单点登陆

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/m0_37776094/article/details/82291973

1.引入maven依赖包

        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.9.0</version>
        </dependency>

        <!-- springboot - Redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

2.池化

package com.dd.common;

import com.dd.util.PropertiesUtil;
import lombok.extern.slf4j.Slf4j;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.JedisShardInfo;
import redis.clients.jedis.ShardedJedis;
import redis.clients.jedis.ShardedJedisPool;
import redis.clients.util.Hashing;
import redis.clients.util.Sharded;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by ckl on 2018/8/31.
 */
@Slf4j
public class RedisShardedPool {
    private static ShardedJedisPool pool;
    private static Integer maxTotal = Integer.parseInt(PropertiesUtil.getProperty("redis.max.total","20")); //最大连接数
    private static Integer maxIdle = Integer.parseInt(PropertiesUtil.getProperty("redis.max.idle","20"));//在jedispool中最大的idle状态(空闲的)的jedis实例的个数
    private static Integer minIdle = Integer.parseInt(PropertiesUtil.getProperty("redis.min.idle","20"));//在jedispool中最小的idle状态(空闲的)的jedis实例的个数

    private static Boolean testOnBorrow = Boolean.parseBoolean(PropertiesUtil.getProperty("redis.test.borrow","true"));//在borrow一个jedis实例的时候,是否要进行验证操作,如果赋值true。则得到的jedis实例肯定是可以用的。
    private static Boolean testOnReturn = Boolean.parseBoolean(PropertiesUtil.getProperty("redis.test.return","true"));//在return一个jedis实例的时候,是否要进行验证操作,如果赋值true。则放回jedispool的jedis实例肯定是可以用的。

    private static String redis1Ip = PropertiesUtil.getProperty("redis1.ip");
    private static Integer redis1Port = Integer.parseInt(PropertiesUtil.getProperty("redis1.port"));
    private static String redis2Ip = PropertiesUtil.getProperty("redis2.ip");
    private static Integer redis2Port = Integer.parseInt(PropertiesUtil.getProperty("redis2.port"));


    private static void initPool(){
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(maxTotal);
        config.setMaxIdle(maxIdle);
        config.setMinIdle(minIdle);
        config.setTestOnBorrow(testOnBorrow);
        config.setTestOnReturn(testOnReturn);

        config.setBlockWhenExhausted(true);//连接耗尽的时候,是否阻塞,false会抛出异常,true阻塞直到超时。默认为true。
        //127.0.0.1:6379
        JedisShardInfo jedis1 = new JedisShardInfo(redis1Ip,redis1Port,1000*2);
        jedis1.setPassword("123456"); //设置redis 6379密码
        //127.0.0.1:6380
        JedisShardInfo jedis2 = new JedisShardInfo(redis2Ip,redis2Port,1000*2);
        jedis2.setPassword("123456");//设置redis 6380密码
        List<JedisShardInfo> jedisShardInfoList = new ArrayList<JedisShardInfo>(2);
        jedisShardInfoList.add(jedis1);
        jedisShardInfoList.add(jedis2);

        /**
         *  public ShardedJedisPool(final GenericObjectPoolConfig poolConfig, List<JedisShardInfo> shards) {
             this(poolConfig, shards, Hashing.MURMUR_HASH);
             }

             public ShardedJedisPool(final GenericObjectPoolConfig poolConfig, List<JedisShardInfo> shards,
             Hashing algo) {
             this(poolConfig, shards, algo, null);
             }

             public ShardedJedisPool(final GenericObjectPoolConfig poolConfig, List<JedisShardInfo> shards,
             Pattern keyTagPattern) {
             this(poolConfig, shards, Hashing.MURMUR_HASH, keyTagPattern);
             }

             public ShardedJedisPool(final GenericObjectPoolConfig poolConfig, List<JedisShardInfo> shards,
             Hashing algo, Pattern keyTagPattern) {
             super(poolConfig, new ShardedJedisFactory(shards, algo, keyTagPattern));
             }
         * final GenericObjectPoolConfig poolConfig:pool的一些设置(maxTotal、maxIdle、minIdle)
         * List<JedisShardInfo> shards:为Redis实例的IP、端口以及超时时间等信息
         * Hashing algo:根据Key计算hash值,从而决定具体存入那个Redis实例
         * Pattern keyTagPattern:Key标签模式,默认是取{}中内容去计算对应Hash值,
         * 当key='{key1_0}_0_0'时,**此时将根据’key1_0'来计算对应hash值,而不是根据{key1_0}_0_0来计算hash值**,
         * 从而决定将存入到哪个Redis实例中,如果需要将数据进行排序,将要利用这个构造函数,因为只有将需要排序的数据
         * 存入同一个Redis实例中,排序才是准确的。
         */
        //一致性Hash分片算法 基于一个圆圈~~
        pool = new ShardedJedisPool(config,jedisShardInfoList, Hashing.MURMUR_HASH, Sharded.DEFAULT_KEY_TAG_PATTERN);
    }

    static{
        initPool();
    }

    /**
     * 获取ShardedJedis实例
     * @return
     */
    public static ShardedJedis getJedis(){
        return pool.getResource();
    }

    /**
     * 关闭ShardedJedis实例,pool回收
     * @param jedis
     */
    public static void returnBrokenResource(ShardedJedis jedis){
        pool.returnBrokenResource(jedis); //3.0被弃用
//        jedis.close();
    }

    /**
     * 关闭ShardedJedis实例,pool回收
     * @param jedis
     */
    public static void returnResource(ShardedJedis jedis){
        pool.returnResource(jedis);
    }

}

##配置文件
redis1.ip=127.0.0.1
redis1.port=6379

redis2.ip=127.0.0.1
redis2.port=6380


#最大连接数
redis.max.total=20

#最大空闲数
redis.max.idle=10

#最小空闲数
redis.min.idle=0

#从jedis连接池获取连接时,校验并返回可用的连接
redis.test.borrow=true

#把连接放回jedis连接池时,校验并返回可用的连接
redis.test.return=false


#redis config end

#closeOrderTaskTime start

close.order.task.time.hour=2

#毫秒数
lock.timeout=5000

#closeOrderTaskTime end

3.RedisShardedPool工具

package com.dd.util;

import com.dd.common.RedisShardedPool;
import lombok.extern.slf4j.Slf4j;
import redis.clients.jedis.ShardedJedis;

/**
 * Sharded Redis工具类
 * Created by ckl on 2018/8/31.
 */
@Slf4j
public class RedisShardedPoolUtil {

    /**
     * 设置key的有效期,单位是秒
     * @param key
     * @param exTime
     * @return
     */
    public static Long expire(String key,int exTime){
        ShardedJedis jedis = null;
        Long result = null;
        try {
            jedis = RedisShardedPool.getJedis();
            result = jedis.expire(key,exTime);
        } catch (Exception e) {
            log.error("expire key:{} error",key,e);
            RedisShardedPool.returnBrokenResource(jedis);
            return result;
        }
        RedisShardedPool.returnResource(jedis);
        return result;
    }

    /**
     * 设置key value exTime的单位是秒
     * @param key
     * @param value
     * @param exTime
     * @return
     */
    public static String setEx(String key,String value,int exTime){
        ShardedJedis jedis = null;
        String result = null;
        try {
            jedis = RedisShardedPool.getJedis();
            result = jedis.setex(key,exTime,value);
        } catch (Exception e) {
            log.error("setex key:{} value:{} error",key,value,e);
            RedisShardedPool.returnBrokenResource(jedis);
            return result;
        }
        RedisShardedPool.returnResource(jedis);
        return result;
    }

    /**
     * 获取
     * @param key
     * @return
     */
    public static String get(String key){
        ShardedJedis jedis = null;
        String result = null;
        try {
            jedis = RedisShardedPool.getJedis();
            result = jedis.get(key);
        } catch (Exception e) {
            log.error("get key:{} error",key,e);
            RedisShardedPool.returnBrokenResource(jedis);
            return result;
        }
        RedisShardedPool.returnResource(jedis);
        return result;
    }

    /**
     * 设置值
     * @param key
     * @param value
     * @return
     */
    public static String set(String key,String value){
        ShardedJedis jedis = null;
        String result = null;

        try {
            jedis = RedisShardedPool.getJedis();
            result = jedis.set(key,value);
        } catch (Exception e) {
            log.error("set key:{} value:{} error",key,value,e);
            RedisShardedPool.returnBrokenResource(jedis);
            return result;
        }
        RedisShardedPool.returnResource(jedis);
        return result;
    }

    /**
     * 获取旧的value,设置新的value;可用于锁操作
     * @param key
     * @param value
     * @return
     */
    public static String getSet(String key,String value){
        ShardedJedis jedis = null;
        String result = null;

        try {
            jedis = RedisShardedPool.getJedis();
            result = jedis.getSet(key,value);
        } catch (Exception e) {
            log.error("getset key:{} value:{} error",key,value,e);
            RedisShardedPool.returnBrokenResource(jedis);
            return result;
        }
        RedisShardedPool.returnResource(jedis);
        return result;
    }

    /**
     * 删除key
     * @param key
     * @return
     */
    public static Long del(String key){
        ShardedJedis jedis = null;
        Long result = null;
        try {
            jedis = RedisShardedPool.getJedis();
            result = jedis.del(key);
        } catch (Exception e) {
            log.error("del key:{} error",key,e);
            RedisShardedPool.returnBrokenResource(jedis);
            return result;
        }
        RedisShardedPool.returnResource(jedis);
        return result;
    }

    /**
     * 当且仅当key不存在,将key的值设置为value,并且返回1;
     * 若是给定的key已经存在,则setnx不做任何动作,返回0。
     * 可用于锁操作
     * @param key
     * @param value
     * @return
     */
    public static Long setnx(String key,String value){
        ShardedJedis jedis = null;
        Long result = null;

        try {
            jedis = RedisShardedPool.getJedis();
            result = jedis.setnx(key,value);
        } catch (Exception e) {
            log.error("setnx key:{} value:{} error",key,value,e);
            RedisShardedPool.returnBrokenResource(jedis);
            return result;
        }
        RedisShardedPool.returnResource(jedis);
        return result;
    }

}

4.Cookie工具

package com.dd.util;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Cookie 工具类
 * Created by ckl on 2018/8/31.
 */
@Slf4j
public class CookieUtil {

    //顶级域
    private final static String COOKIE_DOMAIN = "dandiandenglu.com";
    private final static String COOKIE_NAME = "login_token";

    /**
     * 获取cookie
     * @param request
     * @return
     */
    public static String readLoginToken(HttpServletRequest request){
        Cookie[] cks = request.getCookies();
        if(cks != null){
            for (Cookie ck : cks){
                log.info("read cookieName:{},cookieValue:{}",ck.getName(),ck.getValue());
                if(StringUtils.equals(ck.getName(), COOKIE_NAME)){
                    log.info("return cookieName:{},cookieValue:{}",ck.getName(), ck.getValue());
                    return ck.getValue();
                }
            }
        }
        return  null;
    }


    //X:domain=".dandiandenglu.com"
    //a:A.dandiandenglu.com            cookie:domain=A.dandiandenglu.com;path="/"
    //b:B.dandiandenglu.com            cookie:domain=B.dandiandenglu.com;path="/"
    //c:A.dandiandenglu.com/aa/cc    cookie:domain=A.dandiandenglu.com;path="/aa/cc"
    //d:A.dandiandenglu.com/bb/dd    cookie:domain=A.dandiandenglu.com;path="/aa/bb"
    //e:A.dandiandenglu.com/aa       cookie:domain=A.dandiandenglu.com;path="/aa"
    //原理就是设置获取.dandiandenglu.com顶级域的cookie
    /**
     * 存储cookie
     * @param response
     */
    public static void writeLoginToken(HttpServletResponse response, String token){
        Cookie ck = new Cookie(COOKIE_NAME, token);
        //设置域名 .dandiandenglu.com
        ck.setDomain(COOKIE_DOMAIN);
        //代表设置在根目录
        ck.setPath("/");
        /**
         * 在支持HttpOnly cookies的浏览器中(IE6+,FF3.0+),如果在Cookie中设置了"HttpOnly"属性,
         * 那么通过JavaScript脚本将无法读取到Cookie信息,这样能有效的防止XSS攻击,让网站应用更加安全。
         */
        ck.setHttpOnly(true);
        //单位是秒。
        //如果这个maxage不设置的话,cookie就不会写入硬盘,而是写在内存。只在当前页面有效。
        ck.setMaxAge( 60 * 60 * 24 * 365);
        log.info("write cookieName:{},cookieValue:{}",ck.getName(),ck.getValue());
        response.addCookie(ck);
    }

    /**
     * 删除cookie
     * @param request
     * @param response
     */
    public static void delLoginToken(HttpServletRequest request,HttpServletResponse response){
        Cookie[] cks = request.getCookies();
        if(cks != null){
            for(Cookie ck : cks){
                if(StringUtils.equals(ck.getName(),COOKIE_NAME)){
                    ck.setDomain(COOKIE_DOMAIN);
                    ck.setPath("/");
                    ck.setMaxAge(0);//设置成0,代表删除此cookie。
                    log.info("del cookieName:{},cookieValue:{}",ck.getName(),ck.getValue());
                    response.addCookie(ck);
                    return;
                }
            }
        }
    }


}

5.Controller

package com.dd.controller;

import com.dd.common.ResponseData;
import com.dd.model.User;
import com.dd.service.UserService;
import com.dd.util.Const;
import com.dd.util.CookieUtil;
import com.dd.util.JsonUtil;
import com.dd.util.RedisShardedPoolUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/**
 * Created by ckl on 2018/8/31.
 */
@Slf4j
@RestController
public class LoginController {

    @Autowired
    private UserService userService;

    @RequestMapping("login")
    @ResponseBody
    public ResponseData<User> login(String username,
                                    String password,
                                    HttpSession session,
                                    HttpServletResponse httpServletResponse){
        ResponseData<User> response = userService.login(username,password);
        //登录成功
        if(response.isSuccess()){
//            User user = response.getData();
//            session.setAttribute("ckl",response.getData());
            //设置cookie值 token为:session.getId()
            CookieUtil.writeLoginToken(httpServletResponse,session.getId());
            //保存token到redis
            RedisShardedPoolUtil.setEx(session.getId(), JsonUtil.obj2String(response.getData()), Const.RedisCacheExtime.REDIS_SESSION_EXTIME);
        }
        return response;
    }

    @RequestMapping("info")
    @ResponseBody
    public ResponseData<User> getInfo(HttpSession session,
                                      HttpServletRequest request){
        User user = (User) session.getAttribute("ckl");
        String sessionId = CookieUtil.readLoginToken(request);
        log.info("sessionId {}",sessionId);
        return ResponseData.createBySuccess(user);
    }
}

源码地址:https://download.csdn.net/download/m0_37776094/10640593

猜你喜欢

转载自blog.csdn.net/m0_37776094/article/details/82291973