最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

關(guān)于Redis解決Session共享問題

 更新時(shí)間:2023年07月11日 14:52:13   作者:熬夜磕代碼丶  
這篇文章主要介紹了Redis解決Session共享問題,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下

一、集群Session共享問題

session共享問題:多臺(tái)Tomcat并不共享session存儲(chǔ)空間,當(dāng)請(qǐng)求切換到不同tomcat服務(wù)器時(shí)導(dǎo)致數(shù)據(jù)丟失的問題

tomcat可以進(jìn)行多臺(tái)tomcat進(jìn)行session拷貝,但是數(shù)據(jù)拷貝保存相同的內(nèi)容會(huì)存在資源浪費(fèi),而且會(huì)有時(shí)間延遲,所以這種方案不可行

session的替代方案應(yīng)該滿足:

  • 數(shù)據(jù)共享
  • 內(nèi)存存儲(chǔ)
  • key、value結(jié)構(gòu)

這里我們可以使用redis

二、Redis存儲(chǔ)驗(yàn)證碼和對(duì)象

發(fā)送短信:

@Resource
    private StringRedisTemplate stringRedisTemplate;
    @Override
    public Result sendCode(String phone, HttpSession session) {
        // 1.校驗(yàn)手機(jī)號(hào)
        if (phone == null || str.matches("^1([38][0-9]|4[579]|5[0-3,5-9]|6[6]|7[0135678]|9[89])\\d{8}$")) {
            // 2.如何不符合,返回錯(cuò)誤信息
            return Result.fail("手機(jī)號(hào)格式錯(cuò)誤!");
        }
        // 3.符合,生成驗(yàn)證碼
        String code = RandomUtil.randomNumbers(6);
        // 4.保存驗(yàn)證碼到Redis
        stringRedisTemplate.opsForValue().set("login:code:" + phone,code,2, TimeUnit.MINUTES);
        //具體的發(fā)送邏輯 在這里就不實(shí)現(xiàn)了
        return Result.ok();
    }

首先,我們會(huì)校驗(yàn)前端傳來的手機(jī)號(hào)格式,如果格式不正確直接返回。使用hutool的工具類生成6位隨機(jī)驗(yàn)證碼,然后將驗(yàn)證碼作為value存入到Redis中,為了避免key重復(fù),我們設(shè)置了固定格式的key,并且設(shè)置一個(gè)2分鐘的超時(shí)時(shí)間,超過兩分鐘驗(yàn)證碼自動(dòng)失效。

登錄功能:

public Result login(LoginFormDTO loginForm, HttpSession session) {
        // 1. 校驗(yàn)手機(jī)號(hào)
        String phone = loginForm.getPhone();
        if (phone == null || str.matches("^1([38][0-9]|4[579]|5[0-3,5-9]|6[6]|7[0135678]|9[89])\\d{8}$")) {
            // 如何不符合,返回錯(cuò)誤信息
            return Result.fail("手機(jī)號(hào)格式錯(cuò)誤!");
        }
        // 2. 校驗(yàn)驗(yàn)證碼
        String cacheCode = stringRedisTemplate.opsForValue().get("login:code:" + phone);
        String code = loginForm.getCode();
        // 3. 不一致,報(bào)錯(cuò)
        if(cacheCode == null || !cacheCode.equals(code)) {
            return Result.fail("驗(yàn)證碼錯(cuò)誤!");
        }
        // 4. 一致,根據(jù)手機(jī)號(hào)查詢用戶 select * from tb_user where phone = ?
        User user = query().eq("phone", phone).one();
        // 5. 判斷用戶是否存在
        if (user == null) {
            // 6. 不存在,創(chuàng)建用戶并保存
            user = createUserWithPhone(phone);
        }
        // 7. 保存用戶信息到Redis
        String token = UUID.randomUUID().toString(true);
        UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
        Map<String, Object> userMap = BeanUtil.beanToMap(userDTO,new HashMap<>()
        , CopyOptions.create().setIgnoreNullValue(true).setFieldValueEditor((fieldName,fieldValue) -> fieldValue.toString()));
        stringRedisTemplate.opsForHash().putAll("login:token:" + token,userMap);
        stringRedisTemplate.expire("login:token:" + token,30,TimeUnit.MINUTES);
        return Result.ok(token);
    }

我們在進(jìn)行登錄時(shí),首先會(huì)對(duì)手機(jī)號(hào)格式進(jìn)行檢驗(yàn),如果手機(jī)號(hào)格式正確,我們從Redis中獲取驗(yàn)證碼和客戶端傳來的驗(yàn)證碼進(jìn)行比較,如果一致我們就放行,先去數(shù)據(jù)庫查詢該用戶信息,如果用戶不存在進(jìn)行保存。

可能有的同學(xué)會(huì)有疑問,為什么這里要進(jìn)行這么麻煩的操作呢?

因?yàn)槲覀僓serDTO中的id是Long類型的,會(huì)報(bào)Long轉(zhuǎn)String類型轉(zhuǎn)換異常,因?yàn)槲覀冞@里使用的是StringRedisTemplate

該類型要求key和value都是String類型,但是我們將對(duì)象轉(zhuǎn)為Map時(shí),id為Long類型,所以就出現(xiàn)了該問題,兩種方案:1.自定義Map手動(dòng)put 2.使用BeanUtil,自定義規(guī)則

我們需要將用戶對(duì)象存儲(chǔ)在Redis中,這里用什么作為key呢?我們這里用token作為key,將token返回給客戶端,客戶端后面請(qǐng)求的時(shí)候使用該token來獲取value。

我們value保存對(duì)象時(shí),使用什么存儲(chǔ)呢?

1.String:

2.Hash:

我們這里使用Hash存儲(chǔ)對(duì)象,因?yàn)镠ash結(jié)構(gòu)可以將對(duì)象中的每個(gè)字段獨(dú)立存儲(chǔ),可以針對(duì)單個(gè)字段做CRUD,并且占用內(nèi)存更少。

我們使用UUID隨機(jī)生成token,但是我們value是哈希結(jié)構(gòu),我們使用BeanUtil將對(duì)象轉(zhuǎn)為Hash存儲(chǔ),因?yàn)镽edis是在內(nèi)存存儲(chǔ)的,如果一直只存會(huì)存在內(nèi)存不夠用的情況,所以我們這里仍然需要設(shè)置一個(gè)超時(shí)時(shí)間,那么設(shè)置多長時(shí)間呢?我們這里模仿Session的只要超過30分鐘不訪問就會(huì)銷毀。

但是我們現(xiàn)在設(shè)置的是,從設(shè)置開始不管有沒有用戶訪問30分鐘后都會(huì)銷毀,這樣肯定是不行的,我們需要和session一樣,只要有用戶訪問我們就需要更新超時(shí)時(shí)間,那么怎么做呢?可以借助攔截器

我們的攔截器不是Spring創(chuàng)建的對(duì)象,所以我們無法使用注入的方式獲取StringRedisTemplate對(duì)象,我們需要使用構(gòu)造方法的方法,那么誰來調(diào)用呢?

我們可以在MvcConfig注冊攔截器時(shí)傳入StringRedisTemplate對(duì)象由于我們多處都需要用到ThreadLocal存儲(chǔ)的對(duì)象,所以我們將ThreadLocal封裝成一個(gè)工具類:

public class UserHolder {
    private static final ThreadLocal<UserDTO> tl = new ThreadLocal<>();
    public static void saveUser(UserDTO user){
        tl.set(user);
    }
    public static UserDTO getUser(){
        return tl.get();
    }
    public static void removeUser(){
        tl.remove();
    }
}
public class LoginInterceptor implements HandlerInterceptor {
    private StringRedisTemplate stringRedisTemplate;
    public LoginInterceptor(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1. 獲取請(qǐng)求頭中的token
        String token = request.getHeader("authorization");
        if(StrUtil.isBlank(token)) {
            response.setStatus(401);
            return false;
        }
        // 2. 使用token獲取Redis中的對(duì)象
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries("login:token:" + token);
        // 3. 判斷用戶是否存在
        if(userMap == null) {
            response.setStatus(401);
            return false;
        }
        // 4. 將Hash 格式轉(zhuǎn)為UserDTO對(duì)象
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
        // 5. 將用戶存入ThreadLocal中
        UserHolder.saveUser(userDTO);
        // 6. 刷新token超時(shí)時(shí)間
        stringRedisTemplate.expire("login:token:" + token,30,TimeUnit.MINUTES);
        return true;
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        UserHolder.removeUser();
    }
}

大家需要注意的是我們需要remove ThreadLocal,因?yàn)門hreadLocal可能會(huì)存在內(nèi)存泄露問題,因?yàn)閺?qiáng)軟引用的問題,這里我們不具體介紹。

三、解決狀態(tài)登錄刷新問題

但是這樣會(huì)存在一些問題,該攔截器只會(huì)攔截需要登錄的路徑,其他路徑是不會(huì)攔截了,也就不會(huì)進(jìn)行token有效期的刷新了。怎么解決呢? 新加一個(gè)全部路徑的攔截器

public class RefreshTokenInterceptor implements HandlerInterceptor {
    private StringRedisTemplate stringRedisTemplate;
    public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1. 獲取請(qǐng)求頭中的token
        String token = request.getHeader("authorization");
        if (StrUtil.isBlank(token)) {
            return true;
        }
        // 2. 基于token獲取Redis中的用戶
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(RedisConstants.LOGIN_USER_KEY + token);
        // 3. 判斷用戶是否存在
        if(userMap == null) {
            return true;
        }
        // 將查詢到的Hash轉(zhuǎn)為UserDTO對(duì)象
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
        // 5. 存在 保存用戶到ThreadLocal
        UserHolder.saveUser(userDTO);
        stringRedisTemplate.expire(RedisConstants.LOGIN_USER_KEY + token,RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);
        return true;
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        UserHolder.removeUser();
    }
}

我們創(chuàng)建一個(gè)攔截全部路徑的攔截器來進(jìn)行token有效期的刷新

我們在登錄攔截器里,只需要判斷ThreadLocal里是否存在有效的用戶,如果有放行,否則攔截。

public class MvcConfig implements WebMvcConfigurer {
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .excludePathPatterns(
                   "/user/code",
                   "/user/login",
                   "/blog/hot",
                   "/shop/**",
                   "/shop-type/**",
                   "/upload/**",
                   "/voucher/**"
                );
        registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**");
    }
}

我們在注冊刷新Token的攔截器,并且增加所有路徑。但是我們?nèi)绾伪WC刷新Token的攔截器在登錄攔截器之前執(zhí)行呢?其實(shí)在MvcConfig中注冊攔截器的順序也就是攔截的順序,但是這樣不保險(xiǎn)

其實(shí)我們在addInterceptor時(shí)會(huì)生成一個(gè)攔截器注冊器對(duì)象

攔截器注冊器中又有一個(gè)order屬性,默認(rèn)都是0,這個(gè)值決定攔截器的執(zhí)行順序,值越小執(zhí)行優(yōu)先級(jí)越高。

我們可以通過設(shè)置order來決定它們的執(zhí)行順序

到此這篇關(guān)于Redis解決Session共享問題的文章就介紹到這了,更多相關(guān)Redis解決Session共享內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 基于redis.properties文件的配置及說明介紹

    基于redis.properties文件的配置及說明介紹

    今天小編就為大家分享一篇基于redis.properties文件的配置及說明介紹,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2018-05-05
  • python腳本實(shí)現(xiàn)Redis未授權(quán)批量提權(quán)

    python腳本實(shí)現(xiàn)Redis未授權(quán)批量提權(quán)

    這篇文章主要給大家介紹了關(guān)于利用python腳本實(shí)現(xiàn)redis未授權(quán)批量提權(quán)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。
    2017-09-09
  • Spring?Boot?緩存?與?Redis問題小結(jié)

    Spring?Boot?緩存?與?Redis問題小結(jié)

    本文總結(jié)SpringBoot緩存方案,對(duì)比Redis與EnCache優(yōu)劣,指出Redis支持集群但效率較低,EnCache速度快但不支持分布式,SpringBoot默認(rèn)使用ConcurrentHashMapManager,本文給大家介紹Spring?Boot緩存與Redis問題小結(jié),感興趣的朋友一起看看吧
    2025-07-07
  • 淺談Redis變慢的原因及排查方法

    淺談Redis變慢的原因及排查方法

    本文主要介紹了淺談Redis變慢的原因及排查方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-06-06
  • Redis一鍵巡檢腳本的實(shí)現(xiàn)

    Redis一鍵巡檢腳本的實(shí)現(xiàn)

    在使用Redis作為數(shù)據(jù)存儲(chǔ)的時(shí)候,定期進(jìn)行巡檢是非常重要的,本文主要介紹了Redis一鍵巡檢腳本的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-06-06
  • 用Redis實(shí)現(xiàn)微博關(guān)注關(guān)系

    用Redis實(shí)現(xiàn)微博關(guān)注關(guān)系

    在微博中,每一個(gè)用戶都會(huì)有一個(gè)關(guān)注列表,一個(gè)粉絲列表。用戶可以查看自己的關(guān)注,粉絲列表,也可以查看別人的關(guān)注,粉絲列表。并且,要展示列表里每個(gè)人與當(dāng)前查看者的關(guān)注狀態(tài)。
    2015-09-09
  • Redis SAVE命令不可用問題的原因和解決方案

    Redis SAVE命令不可用問題的原因和解決方案

    遇到 ERR unknown command 'SAVE' 錯(cuò)誤表明Redis 服務(wù)器配置中禁用了 SAVE 命令,這是一個(gè)安全特性,通常在生產(chǎn)環(huán)境中會(huì)被禁用,本文給大家詳細(xì)介紹了解決方案,需要的朋友可以參考下
    2025-07-07
  • Redis 安裝 redistimeseries.so(時(shí)間序列數(shù)據(jù)類型)的配置步驟

    Redis 安裝 redistimeseries.so(時(shí)間序列數(shù)據(jù)類型)的配置步驟

    這篇文章主要介紹了Redis 安裝 redistimeseries.so(時(shí)間序列數(shù)據(jù)類型)詳細(xì)教程,配置步驟需要先下載redistimeseries.so 文件,文中介紹了啟動(dòng)失敗問題排查,需要的朋友可以參考下
    2024-01-01
  • 一次關(guān)于Redis內(nèi)存詭異增長的排查過程實(shí)戰(zhàn)記錄

    一次關(guān)于Redis內(nèi)存詭異增長的排查過程實(shí)戰(zhàn)記錄

    這篇文章主要給大家分享了一次關(guān)于Redis內(nèi)存詭異增長的排查過程實(shí)戰(zhàn)記錄,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Redis具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2018-07-07
  • 嵌入式Redis服務(wù)器在Spring Boot測試中的使用教程

    嵌入式Redis服務(wù)器在Spring Boot測試中的使用教程

    這篇文章主要介紹了嵌入式Redis服務(wù)器在Spring Boot測試中的使用,本文通過實(shí)例代碼場景分析給大家介紹的非常詳細(xì),需要的朋友參考下吧
    2021-07-07

最新評(píng)論

义马市| 安吉县| 康保县| 泾川县| 斗六市| 兴海县| 交口县| 利津县| 安庆市| 桦甸市| 庐江县| 舟曲县| 伊春市| 明光市| 江孜县| 泽州县| 吕梁市| 兰西县| 乌拉特后旗| 教育| 河西区| 北辰区| 祁连县| 林芝县| 龙胜| 定襄县| 区。| 阳城县| 仁布县| 富顺县| 改则县| 南漳县| 肇东市| 个旧市| 光山县| 休宁县| 玛沁县| 临清市| 泰州市| 云龙县| 吉水县|