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

Redis如何解決使用Session登錄帶來(lái)的共享問(wèn)題

 更新時(shí)間:2026年04月23日 17:20:47   作者:怠惰_u  
文章首先介紹了Session的概念及其在實(shí)現(xiàn)登錄功能中的應(yīng)用,然后分析了Session共享問(wèn)題及其原因,并提出使用Redis作為替代方案解決這一問(wèn)題,接著,通過(guò)具體代碼示例展示了如何基于Session和Redis實(shí)現(xiàn)登錄功能,強(qiáng)調(diào)了Redis在解決Session共享問(wèn)題上的優(yōu)勢(shì)

在學(xué)習(xí)項(xiàng)目的過(guò)程中遇到了使用Session實(shí)現(xiàn)登錄功能所帶來(lái)的共享問(wèn)題,此問(wèn)題可以使用Redis來(lái)解決,也即是加上一層來(lái)解決問(wèn)題。

接下來(lái)介紹一些Session的相關(guān)內(nèi)容并且采用Session實(shí)現(xiàn)登錄功能(并附上代碼),進(jìn)行分析其存在的問(wèn)題,并使用Redis來(lái)解決這一問(wèn)題。

一、什么是Session

簡(jiǎn)而言之,session是服務(wù)器為了保存用戶狀態(tài)而創(chuàng)建的一個(gè)特殊的對(duì)象,服務(wù)器會(huì)為每一個(gè)瀏覽器(客戶端)創(chuàng)建一個(gè)唯一的session,session有一個(gè)JSESSIONID,這個(gè)是session的唯一標(biāo)識(shí)。

當(dāng)瀏覽器(客戶端)發(fā)送請(qǐng)求的時(shí)候,會(huì)在Cookie中攜帶JSESSIONID,以便服務(wù)器可以找到對(duì)應(yīng)的Session,服務(wù)器就能知道客戶端的狀態(tài)了。

比如:在登陸一些網(wǎng)站成功之后,你再訪問(wèn)這個(gè)網(wǎng)站的其他內(nèi)容的時(shí)候就不需要重新登陸了,因?yàn)榇藭r(shí)服務(wù)器可以基于瀏覽器Cookie中的JSESSIONID(或者其他校驗(yàn)技術(shù))來(lái)判斷你的狀態(tài)。但是當(dāng)一段時(shí)間未登錄之后,你重新訪問(wèn)網(wǎng)站,往往需要重新登錄,這是因?yàn)闉g覽器訪問(wèn)時(shí)沒(méi)有攜帶JSESSIONID,瀏覽器攜帶的JSESSIONID對(duì)應(yīng)的session不存在(或者失效)的原因。

二、基于Session實(shí)現(xiàn)登錄(短信登錄)

1.流程

2.代碼實(shí)現(xiàn)

(1)Controller層:(接收請(qǐng)求)

    /**
     * 發(fā)送手機(jī)驗(yàn)證碼
     */
    @PostMapping("code")
    public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {
        //發(fā)送短信驗(yàn)證碼并保存驗(yàn)證碼
        return userService.sendCode(phone, session);
    }

    /**
     * 登錄功能
     * @param loginForm 登錄參數(shù),包含手機(jī)號(hào)、驗(yàn)證碼;或者手機(jī)號(hào)、密碼
     */
    @PostMapping("/login")
    public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){
        // 實(shí)現(xiàn)登錄功能
        return userService.login(loginForm, session);
    }

(2)Service層:(處理業(yè)務(wù)邏輯)

    /**
     * 發(fā)送短信驗(yàn)證碼并保存驗(yàn)證碼
     * @param phone
     * @param session
     * @return
     */
    @Override
    public Result sendCode(String phone, HttpSession session) {
        //1.校驗(yàn)手機(jī)號(hào)是否合法
        boolean phoneInvalid = RegexUtils.isPhoneInvalid(phone);
        //2.若不合法,返回錯(cuò)誤信息
        if(!phoneInvalid){
            return Result.fail("手機(jī)號(hào)不合法");
        }
        //3.生成驗(yàn)證碼 6位
        String code = RandomUtil.randomNumbers(6);
        //4.保存驗(yàn)證碼到session中
        session.setAttribute("code",code);
        //5.發(fā)送驗(yàn)證碼 (這里可以結(jié)合阿里云提供的第三方服務(wù)來(lái)做,但是為了方便,輸出一下就可以)
        log.debug("發(fā)送短信驗(yàn)證碼成功,驗(yàn)證碼:{}",code);
        //6.返回ok
        return Result.ok();

    }
     /**
     * 登錄功能
     * @param loginForm
     * @param session
     * @return
     */
    @Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {

        String phone = loginForm.getPhone();
        //1.校驗(yàn)手機(jī)號(hào)是否合法
        if(!RegexUtils.isPhoneInvalid(phone)){
            return Result.fail("手機(jī)號(hào)不合法");
        }
        //2.校驗(yàn)驗(yàn)證碼是否一致
        String sessionCode = (String) session.getAttribute("code");
        String userCode = loginForm.getCode();
        //3.驗(yàn)證碼不一致返回錯(cuò)誤信息
        if(sessionCode == null ||!sessionCode.equals(userCode)){
            return Result.fail("驗(yàn)證碼錯(cuò)誤");
        }
        //4.根據(jù)手機(jī)號(hào)到數(shù)據(jù)庫(kù)查詢用戶
        User user = query().eq("phone", phone).one();
        //5.若用戶不存在則創(chuàng)建用戶并保存到數(shù)據(jù)庫(kù)
        if(user == null){
            user = createUserByPhone(phone);
        }
        //6.保存用戶信息到session中
        UserDTO userDTO = new UserDTO();
        BeanUtils.copyProperties(user,userDTO);
        session.setAttribute("user",userDTO);

        //7.返回ok
        return Result.ok();
    }

(3)LoginInterceptor:(攔截請(qǐng)求,進(jìn)行登錄校驗(yàn))

public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //1.獲取session
        HttpSession session = request.getSession();

        //2.獲取session里面的user
        User user = (User) session.getAttribute("user");

        //3.用戶不存在,則攔截
        if(user == null){
            //4.攔截,返回401
            response.setStatus(401);
            return false;
        }
        //5.存在,保存到ThreadLocal中
        UserDTO userDTO = new UserDTO();
        userDTO.setId(user.getId());
        userDTO.setIcon(user.getIcon());
        userDTO.setNickName(user.getNickName());
        UserHolder.saveUser(userDTO);

        //6.放行
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //在頁(yè)面渲染完成返回給用戶前,刪除用戶信息,避免內(nèi)存泄漏
        UserHolder.removeUser();
    }
}

(4)MVCconfig:(用于注冊(cè)攔截器,使得攔截器生效)

@Configuration//由Spring創(chuàng)建的對(duì)象 可以使用 Autowired
public class MVCconfig implements WebMvcConfigurer {

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

三、基于Session實(shí)現(xiàn)登錄存在的共享問(wèn)題

1.問(wèn)題分析

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

也就是說(shuō)一臺(tái)瀏覽器可能會(huì)被負(fù)載均衡到不同的服務(wù)器上,但是服務(wù)器的Session是不共享的,那當(dāng)瀏覽器先被負(fù)載均衡到服務(wù)器A,后負(fù)載均衡到服務(wù)器B時(shí),瀏覽器所攜帶的JSESSIONID只在服務(wù)器A生效,而服務(wù)器B找不到其對(duì)應(yīng)的Session,也就無(wú)法獲知用戶的狀態(tài)。這就是基于Session實(shí)現(xiàn)登錄存在的共享問(wèn)題。

2.解決方案:

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

(1)數(shù)據(jù)共享:

首先必須滿足數(shù)據(jù)可共享,否則還是會(huì)引發(fā)共享問(wèn)題。這個(gè)時(shí)候就應(yīng)該想到常用的思路“加一層”,加上一層中間件,上層都到這個(gè)中間件來(lái)存取數(shù)據(jù),這樣不就實(shí)現(xiàn)了數(shù)據(jù)共享嗎。

(2)內(nèi)存存儲(chǔ)

由于每次請(qǐng)求的時(shí)候,服務(wù)器都需要對(duì)請(qǐng)求進(jìn)行校驗(yàn),這個(gè)操作是十分頻繁的,如果可以將數(shù)據(jù)存儲(chǔ)在內(nèi)存中,就可以極大地提高服務(wù)器處理請(qǐng)求的效率。

(3)key、value結(jié)構(gòu)

服務(wù)器應(yīng)該根據(jù)服務(wù)器所攜帶key去找到其對(duì)應(yīng)的value

通過(guò)以上分析,使用Redis來(lái)解決這個(gè)問(wèn)題是再合適不過(guò)的了。

使用Redis實(shí)現(xiàn)登錄功能總思路:

1.用戶提交手機(jī)號(hào),后端收到手機(jī)號(hào)之后生成驗(yàn)證碼并以手機(jī)號(hào)為key,驗(yàn)證碼為value,并設(shè)置過(guò)期時(shí)間,存儲(chǔ)到redis中。存儲(chǔ)完成之后發(fā)送驗(yàn)證碼給用戶。

2.用戶填寫驗(yàn)證碼,瀏覽器發(fā)送協(xié)帶用戶填寫的手機(jī)號(hào)和驗(yàn)證碼的請(qǐng)求。后端接收請(qǐng)求,根據(jù)用戶的手機(jī)號(hào)到redis中查找對(duì)應(yīng)的驗(yàn)證碼值,查找后將redis中的驗(yàn)證碼和用戶提交的驗(yàn)證碼進(jìn)行比較。

3.驗(yàn)證成功,則生成一個(gè)token,將token作為key,用戶信息作為value(這里使用redis的hash結(jié)構(gòu)存儲(chǔ)),設(shè)置過(guò)期時(shí)間,存入redis中,并返回一個(gè)token。

4.此后,前端發(fā)送請(qǐng)求的時(shí)候,就攜帶這個(gè)token,服務(wù)器就可以根據(jù)這個(gè)token到redis中去獲取用戶的信息。

5.由于redis是單獨(dú)的一層,所有瀏覽器拿到token之后都是去這個(gè)redis中查找數(shù)據(jù),這就解決了用戶被負(fù)載均衡到不同服務(wù)器時(shí),session引發(fā)的共享問(wèn)題。

四、基于Redis實(shí)現(xiàn)登錄

1.流程

簡(jiǎn)單來(lái)說(shuō)就是把對(duì)session存取操作改為對(duì)redis的存取操作。

2.代碼實(shí)現(xiàn)

(1)Controller層

     /**
     * 發(fā)送手機(jī)驗(yàn)證碼
     */
    @PostMapping("code")
    public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {
        //發(fā)送短信驗(yàn)證碼并保存驗(yàn)證碼
        return userService.sendCode(phone, session);
    }

    /**
     * 登錄功能
     * @param loginForm 登錄參數(shù),包含手機(jī)號(hào)、驗(yàn)證碼;或者手機(jī)號(hào)、密碼
     */
    @PostMapping("/login")
    public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){
        // 實(shí)現(xiàn)登錄功能
        return userService.login(loginForm, session);
    }

    @GetMapping("/me")
    public Result me(){
        //獲取當(dāng)前登錄的用戶并返回
        UserDTO user = UserHolder.getUser();
        return Result.ok(user);
    }

(2)Service層:(處理業(yè)務(wù)邏輯)

    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    /**
     * 發(fā)送短信驗(yàn)證碼并保存驗(yàn)證碼
     * @param phone
     * @param session
     * @return
     */
    @Override
    public Result sendCode(String phone, HttpSession session) {
        //1.校驗(yàn)手機(jī)號(hào)是否合法
        boolean phoneInvalid = RegexUtils.isPhoneInvalid(phone);

        //2.若不合法,返回錯(cuò)誤信息
        if(!phoneInvalid){
            return Result.fail("手機(jī)號(hào)不合法");
        }

        //3.生成驗(yàn)證碼 6位
        String code = RandomUtil.randomNumbers(6);

        //4.保存驗(yàn)證碼到redis中
        stringRedisTemplate.opsForValue().set(RedisConstants.LOGIN_CODE_KEY+phone,code,RedisConstants.LOGIN_CODE_TTL, TimeUnit.MINUTES);

        //5.發(fā)送驗(yàn)證碼 (這里可以結(jié)合阿里云提供的第三方服務(wù)來(lái)做,但是為了方便,輸出一下就可以)
        log.debug("發(fā)送短信驗(yàn)證碼成功,驗(yàn)證碼:{}",code);

        //6.返回ok
        return Result.ok();
    }

    /**
     * 登錄功能
     * @param loginForm
     * @param session
     * @return
     */
    @Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {

        String phone = loginForm.getPhone();
        //1.校驗(yàn)手機(jī)號(hào)是否合法
        if(!RegexUtils.isPhoneInvalid(phone)){
            return Result.fail("手機(jī)號(hào)不合法");
        }
        //2.TODO 從Redis獲取驗(yàn)證碼并校驗(yàn)驗(yàn)證碼是否一致
        String reidsCode = stringRedisTemplate.opsForValue().get(RedisConstants.LOGIN_CODE_KEY+phone);
        String userCode = loginForm.getCode();
        //3.驗(yàn)證碼不一致返回錯(cuò)誤信息
        if(reidsCode == null ||!reidsCode.equals(userCode)){
            return Result.fail("驗(yàn)證碼錯(cuò)誤");
        }
        //4.根據(jù)手機(jī)號(hào)到數(shù)據(jù)庫(kù)查詢用戶
        User user = query().eq("phone", phone).one();
        //5.若用戶不存在則創(chuàng)建用戶并保存到數(shù)據(jù)庫(kù)
        if(user == null){
            user = createUserByPhone(phone);
        }
        //6.保存用戶信息到redis中
        //6.1 生成隨機(jī)token 作為登錄令牌
        String token = UUID.randomUUID().toString(true);
        //6.2 將UserDTO轉(zhuǎn)為 HashMap
        UserDTO userDTO = new UserDTO();
        BeanUtils.copyProperties(user,userDTO);

        Map<String, Object>usertMap = new HashMap<>();
        usertMap.put("id", userDTO.getId().toString());
        usertMap.put("nickName", userDTO.getNickName());
        usertMap.put("icon", userDTO.getIcon());
        //6.3 存儲(chǔ)到redis中
        String tokenKey = RedisConstants.LOGIN_USER_KEY+token;
        stringRedisTemplate.opsForHash().putAll(tokenKey,usertMap);
        //6.4 設(shè)置有效期
        stringRedisTemplate.expire(tokenKey,RedisConstants.LOGIN_USER_TTL,TimeUnit.MINUTES);

        //7.返回token
        return Result.ok(token);
    }

    /**
     * 根據(jù)手機(jī)號(hào)創(chuàng)建用戶
     * @param phone
     * @return
     */
    private User createUserByPhone(String phone) {
        User user = new User();
        user.setPhone(phone);
        user.setNickName(SystemConstants.USER_NICK_NAME_PREFIX + RandomUtil.randomString(6));
        save(user);
        return user;
    }

(3)LoginInterceptor:(攔截請(qǐng)求,進(jìn)行登錄校驗(yàn))

      @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1.獲取請(qǐng)求頭中的token
        String token = request.getHeader("authorization");
        if(StringUtils.isEmpty(token)){
            //token 為空 攔截,返回401
            response.setStatus(401);
            return false;
        }

        //2.通過(guò)token獲取redis中的用戶信息
        String tokenKey = RedisConstants.LOGIN_USER_KEY + token;
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash()
                .entries(tokenKey);

        //3.用戶不存在,則攔截
        if(userMap.isEmpty()){
            //4.攔截,返回401
            response.setStatus(401);
            return false;
        }
        //5. 將HashMap轉(zhuǎn)為UserDTO
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);

        //6.存在,保存到ThreadLocal中
        UserHolder.saveUser(userDTO);

        //7.重置token有效期
        stringRedisTemplate.expire(tokenKey,RedisConstants.LOGIN_USER_TTL,TimeUnit.MINUTES)
        //8.放行
        return true;
    }

(4)MVCconfig:(用于注冊(cè)攔截器,使得攔截器生效)

@Configuration//由Spring創(chuàng)建的對(duì)象 可以使用 Autowired
public class MVCconfig implements WebMvcConfigurer {

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

總結(jié)

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Feign遠(yuǎn)程調(diào)用丟失請(qǐng)求頭問(wèn)題

    Feign遠(yuǎn)程調(diào)用丟失請(qǐng)求頭問(wèn)題

    本文介紹了在服務(wù)端項(xiàng)目中如何解決資源訪問(wèn)限制問(wèn)題,首先介紹了問(wèn)題的產(chǎn)生,然后詳細(xì)解析了源碼,最后提出了解決方案,解決方案包括同步和異步兩種,同步時(shí)直接向Spring容器注入RequestInterceptor攔截器
    2024-09-09
  • 在本地用idea連接虛擬機(jī)上的hbase集群的實(shí)現(xiàn)代碼

    在本地用idea連接虛擬機(jī)上的hbase集群的實(shí)現(xiàn)代碼

    這篇文章主要介紹了在本地用idea連接虛擬機(jī)上的hbase集群的實(shí)現(xiàn)代碼,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-10-10
  • Java執(zhí)行Python代碼的五種場(chǎng)景與示例方法

    Java執(zhí)行Python代碼的五種場(chǎng)景與示例方法

    python可以搭建后端讓Java調(diào)用接口,但某些時(shí)候我們用到的python代碼可能并不多也許只有一個(gè)算法,此時(shí)就需要其他方法了,下面小編就來(lái)和大家詳細(xì)介紹一下吧
    2025-07-07
  • java異常處理的簡(jiǎn)單練習(xí)

    java異常處理的簡(jiǎn)單練習(xí)

    下面小編就為大家?guī)?lái)一篇java異常處理的簡(jiǎn)單練習(xí)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2016-06-06
  • SpringBoot集成kafka全面實(shí)戰(zhàn)記錄

    SpringBoot集成kafka全面實(shí)戰(zhàn)記錄

    在實(shí)際開(kāi)發(fā)中,我們可能有這樣的需求,應(yīng)用A從TopicA獲取到消息,經(jīng)過(guò)處理后轉(zhuǎn)發(fā)到TopicB,再由應(yīng)用B監(jiān)聽(tīng)處理消息,即一個(gè)應(yīng)用處理完成后將該消息轉(zhuǎn)發(fā)至其他應(yīng)用,完成消息的轉(zhuǎn)發(fā),這篇文章主要介紹了SpringBoot集成kafka全面實(shí)戰(zhàn),需要的朋友可以參考下
    2021-11-11
  • 靈活控制任務(wù)執(zhí)行時(shí)間的Cron表達(dá)式范例

    靈活控制任務(wù)執(zhí)行時(shí)間的Cron表達(dá)式范例

    這篇文章主要為大家介紹了靈活控制任務(wù)執(zhí)行時(shí)間的Cron表達(dá)式范例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-10-10
  • HashSet和TreeSet使用方法的區(qū)別解析

    HashSet和TreeSet使用方法的區(qū)別解析

    這篇文章主要介紹了HashSet和TreeSet使用方法的區(qū)別解析,具有一定借鑒價(jià)值,需要的朋友可以參考下
    2018-01-01
  • maven多profile 打包下 -P參和-D參數(shù)的實(shí)現(xiàn)

    maven多profile 打包下 -P參和-D參數(shù)的實(shí)現(xiàn)

    這篇文章主要介紹了maven多profile 打包下 -P參和-D參數(shù)的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-11-11
  • 詳解IDEA中SpringBoot整合Servlet三大組件的過(guò)程

    詳解IDEA中SpringBoot整合Servlet三大組件的過(guò)程

    這篇文章主要介紹了詳解IDEA中SpringBoot整合Servlet三大組件的過(guò)程,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-11-11
  • Android 資源 id詳解及的動(dòng)態(tài)獲取

    Android 資源 id詳解及的動(dòng)態(tài)獲取

    這篇文章主要介紹了Android 資源 id詳解及的動(dòng)態(tài)獲取的相關(guān)資料,需要的朋友可以參考下
    2016-12-12

最新評(píng)論

府谷县| 三明市| 江油市| 收藏| 南木林县| 滦南县| 崇信县| 黄平县| 武冈市| 新昌县| 天峻县| 江永县| 台南县| 宁都县| 长宁县| 镇沅| 台州市| 博客| 松原市| 留坝县| 娱乐| 万全县| 长丰县| 策勒县| 孟津县| 遂宁市| 盐源县| 板桥市| 鹰潭市| 庆元县| 大丰市| 建昌县| 额济纳旗| 临海市| 南安市| 都昌县| 长岭县| 昌黎县| 金昌市| 伊通| 威远县|