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

基于Redis實(shí)現(xiàn)登錄功能思路詳解(手機(jī)號(hào)+驗(yàn)證碼)

 更新時(shí)間:2026年01月08日 11:06:28   作者:什么都不會(huì)的Tristan  
本文介紹了使用手機(jī)號(hào)和驗(yàn)證碼登錄的方式,驗(yàn)證碼通過控制臺(tái)輸出,重點(diǎn)解釋了UserServiceImpl實(shí)現(xiàn)類的結(jié)構(gòu),包括sendCode、login和createUserWithPhone方法,還介紹了攔截器框架,包括preHandle和攔截器鏈的配置,感興趣的朋友跟隨小編一起看看吧

本文使用的是 手機(jī)號(hào)+驗(yàn)證碼 的登錄方式,其中驗(yàn)證碼是通過在控制臺(tái)輸出,并沒有真的發(fā)送到手機(jī)上(太麻煩,主要目的還是學(xué)習(xí)使用Redis)

重點(diǎn)是看思路,而不是具體的代碼實(shí)現(xiàn)

UserServiceImpl實(shí)現(xiàn)類

整體結(jié)構(gòu)

@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Override
    public Result sendCode(String phone, HttpSession session) {
        //...
    }
    @Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {
        //...
    }
    private User createUserWithPhone(String phone) {
        //...
    }
}

sendCode方法

這個(gè)是發(fā)送驗(yàn)證碼的方法

public Result sendCode(String phone, HttpSession session) {
    // 1. 校驗(yàn)手機(jī)號(hào)
    if (RegexUtils.isPhoneInvalid(phone)) {
        // 2. 如果不符合,返回錯(cuò)誤信息
        return Result.fail("手機(jī)號(hào)格式錯(cuò)誤!");
    }
    // 3. 如果符合,生成驗(yàn)證碼
    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)證碼
    log.debug("發(fā)送短信驗(yàn)證碼成功,驗(yàn)證碼:{}", code);
    // 6. 返回結(jié)果
    return Result.ok();
}

注:這里的RedisConstants是一個(gè)用來存放各種常量的類

public class RedisConstants {
    public static final String LOGIN_CODE_KEY = "login:code:";
    public static final Long LOGIN_CODE_TTL = 2L;
    public static final String LOGIN_USER_KEY = "login:token:";
    public static final Long LOGIN_USER_TTL = 30L;
}

login方法

這里使用了MybatisPlus來操作數(shù)據(jù)庫(User user = query().eq("phone", phone).one();),但是這個(gè)不是重點(diǎn)

public Result login(LoginFormDTO loginForm, HttpSession session) {
    // 1. 校驗(yàn)手機(jī)號(hào)
    String phone = loginForm.getPhone();
    if (RegexUtils.isPhoneInvalid(phone)) {
        return Result.fail("手機(jī)號(hào)格式錯(cuò)誤!");
    }
    // 2. 從redis獲取驗(yàn)證碼并校驗(yàn)
    String cacheCode = stringRedisTemplate.opsForValue().get(RedisConstants.LOGIN_CODE_KEY +phone);
    String code = loginForm.getCode();
    if (cacheCode == null || !cacheCode.equals(code)) {
        // 3. 不一致,報(bào)錯(cuò)
        return Result.fail("驗(yàn)證碼錯(cuò)誤!");
    }
    // 4. 一致,根據(jù)手機(jī)號(hào)查詢用戶
    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(RedisConstants.LOGIN_USER_KEY + token, userMap);
    stringRedisTemplate.expire(RedisConstants.LOGIN_USER_KEY + token, RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);
    return Result.ok(token);
}

createUserWithPhone方法

在login方法中調(diào)用了該方法

這里也使用了MybatisPlus來操作數(shù)據(jù)庫(save(user);)

private User createUserWithPhone(String phone) {
    // 1. 創(chuàng)建用戶
    User user = new User();
    user.setPhone(phone);
    user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));
    // 2. 保存用戶
    save(user);
    return user;
}

攔截器

整體框架

其實(shí)就是實(shí)現(xiàn)了HandlerInterceptor的兩個(gè)方法

@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //...
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 移除用戶
        UserHolder.removeUser();
    }
}

 UserHolder是ThreadLocal 持有類

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();
    }
}

preHandle方法

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中的用戶
    String key = RedisConstants.LOGIN_USER_KEY + token;
    Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);
    // 3.判斷用戶是否存在
    if (userMap.isEmpty()) {
        // 4.不存在,攔截
        response.setStatus(401);
        return false;
    }
    // 5.將查詢到的Hash數(shù)據(jù)轉(zhuǎn)換為UserDTO對(duì)象
    UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
    // 6.存在,保存用戶信息到ThreadLocal
    UserHolder.saveUser(userDTO);
    // 7.刷新token有效期
    stringRedisTemplate.expire(key, RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);
    // 8.放行
    return true;
}

注:authorization 是前端定義的用來傳遞token的key

配置類

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

整體思路

flowchart TD
subgraph A[發(fā)送驗(yàn)證碼流程]
    A1["前端請(qǐng)求 發(fā)送驗(yàn)證碼"] --> A2["校驗(yàn)手機(jī)號(hào)格式"]
    A2 -- 不合法 --> A3["返回錯(cuò)誤  手機(jī)號(hào)格式錯(cuò)誤"]
    A2 -- 合法 --> A4["生成6位驗(yàn)證碼"]
    A4 --> A5["保存驗(yàn)證碼到Redis"]
    A5 --> A6["返回成功"]
end
subgraph B[登錄流程]
    B1["前端請(qǐng)求 登錄"] --> B2["校驗(yàn)手機(jī)號(hào)格式"]
    B2 -- 不合法 --> B3["返回錯(cuò)誤"]
    B2 -- 合法 --> B4["從Redis獲取驗(yàn)證碼"]
    B4 --> B5{"驗(yàn)證碼是否正確"}
    B5 -- 否 --> B6["返回驗(yàn)證碼錯(cuò)誤"]
    B5 -- 是 --> B7["根據(jù)手機(jī)號(hào)查詢用戶"]
    B7 --> B8{"用戶是否存在"}
    B8 -- 否 --> B9["創(chuàng)建新用戶"]
    B8 -- 是 --> B10["使用已有用戶"]
    B9 --> B11["生成Token"]
    B10 --> B11
    B11 --> B12["用戶信息寫入Redis"]
    B12 --> B13["返回Token"]
end
subgraph C[請(qǐng)求攔截流程]
    C1["請(qǐng)求到達(dá)攔截器"] --> C2["從請(qǐng)求頭獲取Token"]
    C2 --> C3{"Token是否存在"}
    C3 -- 否 --> C4["返回401"]
    C3 -- 是 --> C5["從Redis獲取用戶信息"]
    C5 --> C6{"用戶是否存在"}
    C6 -- 否 --> C4
    C6 -- 是 --> C7["保存用戶到ThreadLocal"]
    C7 --> C8["刷新Token有效期"]
    C8 --> C9["放行請(qǐng)求"]
end
subgraph D[請(qǐng)求結(jié)束]
    D1["請(qǐng)求完成"] --> D2["清理ThreadLocal"]
end
B13 --> C1
C9 --> D1

復(fù)制到未命名繪圖 - draw.io中用mermaid格式文件創(chuàng)建流程圖

優(yōu)化

目前之后訪問被攔截的頁面才會(huì)刷新有效期,所以這里我們需要優(yōu)化一下

方式是采用攔截器鏈,即再加一個(gè)攔截器來攔截全部頁面,以此來更新有效期

RefreshTokenInterceptor

@Slf4j
@Component
public class RefreshTokenInterceptor implements HandlerInterceptor {
    @Autowired
    private 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中的用戶
        String key = RedisConstants.LOGIN_USER_KEY + token;
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);
        // 3.判斷用戶是否存在
        if (userMap.isEmpty()) {
            return true;
        }
        // 5.將查詢到的Hash數(shù)據(jù)轉(zhuǎn)換為UserDTO對(duì)象
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
        // 6.存在,保存用戶信息到ThreadLocal
        UserHolder.saveUser(userDTO);
        // 7.刷新token有效期
        stringRedisTemplate.expire(key, RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);
        // 8.放行
        return true;
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 移除用戶
        UserHolder.removeUser();
    }
}

LoginInterceptor

@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 判斷是否需要攔截(ThreadLocal中是否有用戶)
        if (UserHolder.getUser() == null) {
            response.setStatus(401);
            return false;
        }
        // 有用戶,則放行
        return true;
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 移除用戶
        UserHolder.removeUser();
    }
}

配置類

@Configuration
public class MvcConfig implements WebMvcConfigurer {
    @Autowired
    private LoginInterceptor loginInterceptor;
    @Autowired
    private RefreshTokenInterceptor refreshTokenInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 登錄攔截器
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns(
                        "/user/code",
                        "/user/login",
                        "/blog/hot",
                        "/shop/**",
                        "/shop-type/**",
                        "/upload/**",
                        "/voucher/**"
                ).order(1);
        // 刷新token攔截器
        registry.addInterceptor(refreshTokenInterceptor)
                .addPathPatterns("/**").order(0);
    }
}

注:order方法是用來設(shè)置哪一個(gè)攔截器在前,哪一個(gè)在后;規(guī)則:數(shù)字小的在前,數(shù)字大的在后

到此這篇關(guān)于基于Redis實(shí)現(xiàn)登錄功能的文章就介紹到這了,更多相關(guān)redis登錄內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Python利用redis限制用戶重復(fù)刷新帶來的數(shù)據(jù)問題

    Python利用redis限制用戶重復(fù)刷新帶來的數(shù)據(jù)問題

    在網(wǎng)站開發(fā)中,我們經(jīng)常會(huì)遇到需要控制用戶重復(fù)刷新頁面的情況,本文就來介紹了Python利用redis限制用戶重復(fù)刷新帶來的數(shù)據(jù)問題,感興趣的可以了解一下
    2024-03-03
  • Redis中的配置文件,數(shù)據(jù)持久化,事務(wù)

    Redis中的配置文件,數(shù)據(jù)持久化,事務(wù)

    這篇文章主要介紹了Redis中的配置文件,數(shù)據(jù)持久化,事務(wù)問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。
    2022-12-12
  • Redis中主鍵失效的原理及實(shí)現(xiàn)機(jī)制剖析

    Redis中主鍵失效的原理及實(shí)現(xiàn)機(jī)制剖析

    這篇文章主要介紹了Redis中主鍵失效的原理及實(shí)現(xiàn)機(jī)制剖析,本文講解了失效時(shí)間的控制、失效的內(nèi)部實(shí)現(xiàn)、Memcached 刪除失效主鍵的方法與 Redis 有何異同、Redis 的主鍵失效機(jī)制會(huì)不會(huì)影響系統(tǒng)性能等內(nèi)容,需要的朋友可以參考下
    2015-06-06
  • Redis分布式緩存方式(RDB、AOF、主從同步)

    Redis分布式緩存方式(RDB、AOF、主從同步)

    這篇文章主要介紹了Redis分布式緩存方式(RDB、AOF、主從同步),具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2026-03-03
  • Redis消息隊(duì)列的三種實(shí)現(xiàn)方式

    Redis消息隊(duì)列的三種實(shí)現(xiàn)方式

    本文主要介紹了Redis消息隊(duì)列的三種實(shí)現(xiàn)方式,主要包括List實(shí)現(xiàn)消息隊(duì)列,PubSub消息隊(duì)列,Stream消息隊(duì)列,具有一定的參考價(jià)值,感興趣的可以了解一下
    2023-12-12
  • 基于Redis實(shí)現(xiàn)抽獎(jiǎng)功能及問題小結(jié)

    基于Redis實(shí)現(xiàn)抽獎(jiǎng)功能及問題小結(jié)

    這篇文章主要介紹了基于Redis實(shí)現(xiàn)抽獎(jiǎng)功能,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-08-08
  • redis中全局命令exists、del、expire、ttl(惰性刪除和定期刪除)

    redis中全局命令exists、del、expire、ttl(惰性刪除和定期刪除)

    本文主要介紹了redis中全局命令exists、del、expire、ttl(惰性刪除和定期刪除),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2025-11-11
  • 基于Redis的分布式鎖及原子性問題(短視頻開發(fā))

    基于Redis的分布式鎖及原子性問題(短視頻開發(fā))

    短視頻開發(fā)中,Redis分布式鎖通過SETNX實(shí)現(xiàn)加鎖與解鎖,需設(shè)置超時(shí)時(shí)間避免死鎖,為防止誤刪,釋放鎖時(shí)需判斷線程身份,并用Lua腳本保證原子性,確保安全操作,本文給大家介紹基于Redis的分布式鎖及原子性問題,感興趣的朋友一起看看吧
    2025-06-06
  • 基于redis實(shí)現(xiàn)token驗(yàn)證用戶是否登陸

    基于redis實(shí)現(xiàn)token驗(yàn)證用戶是否登陸

    這篇文章主要為大家詳細(xì)介紹了基于redis實(shí)現(xiàn)token驗(yàn)證用戶是否登陸,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-08-08
  • 在Windows系統(tǒng)上部署Redis的多種解決方案

    在Windows系統(tǒng)上部署Redis的多種解決方案

    本文詳細(xì)介紹了在Windows上部署Redis的方法,包括選擇版本與資源、環(huán)境準(zhǔn)備、核心部署實(shí)戰(zhàn)、核心配置文件詳解、遠(yuǎn)程連接與防火墻設(shè)置等內(nèi)容,文章還推薦了幾款適合的可視化客戶端,并簡要介紹了替代部署方案,需要的朋友可以參考下
    2026-05-05

最新評(píng)論

景德镇市| 浮梁县| 丹江口市| 普宁市| 突泉县| 栖霞市| 沙洋县| 依兰县| 平武县| 闻喜县| 武宣县| 榆中县| 金湖县| 九江市| 浦城县| 张家界市| 南华县| 胶州市| 西华县| 嘉禾县| 凤凰县| 龙口市| 县级市| 浠水县| 阜阳市| 刚察县| 彩票| 雅安市| 潜江市| 莱阳市| 玉龙| 镇平县| 五大连池市| 越西县| 宝坻区| 定远县| 鄢陵县| 缙云县| 千阳县| 松桃| 宜城市|