首頁 > 軟體

如何基於Session實現簡訊登入功能

2022-11-01 14:03:34

一、基於Session實現登入

1.1 業務流程圖

二、傳送簡訊驗證碼

2.1 傳送簡訊請求方式及引數說明

這個地方為什麼需要session?  因為我們需要把驗證碼儲存在session當中

     /**
     * 傳送手機驗證碼
     */
    @PostMapping("code")
    public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {
        // TODO 傳送簡訊驗證碼並儲存驗證碼
//        return Result.fail("功能未完成");
        return userService.sendCode(phone,session);
    }

2.2 業務層程式碼模擬傳送簡訊

    @Override
    public Result sendCode(String phone, HttpSession session) {
//      1.校驗手機號
        if(RegexUtils.isPhoneInvalid(phone)){
//              說明:RegexUtils使我們封裝的一個類   isCodeInvalid是裡面的靜態方法,在這個靜態方法裡面又呼叫了另外一個靜態方法得以實現
//      2.如果不符合,返回錯誤資訊
            return Result.fail("手機號格式錯誤");
        }
 
//      3.符合,生成驗證碼    6代表生成的驗證碼的長度  RandomUtil使用這個工具類生成
        String code =  RandomUtil.randomNumbers(6);
//      4.儲存驗證碼到session       key必須是一個字串,value是一個物件
        session.setAttribute("code",code);
//      5.傳送驗證碼
//        實現起來比較麻煩 我們使用紀錄檔假裝傳送
        log.debug("傳送簡訊驗證碼成功,驗證碼:"+code);
        return Result.ok();
    }
}

三、登入功能  

3.1  簡訊驗證的請求方式及路徑

    /**
     * 登入功能
     * @param loginForm 登入引數,包含手機號、驗證碼;或者手機號、密碼
     */
    @PostMapping("/login")
    public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){
        // TODO 實現登入功能
        return userService.login(loginForm,session);
    }

3.2  業務層程式碼實現使用者登入

流程圖:

程式碼:

 
    /**
     * 實現使用者登入
     * @param loginForm  登入的引數
     * @param session
     * @return
     */
    @Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {
//      1.校驗手機號
        if(RegexUtils.isPhoneInvalid(loginForm.getPhone())){
//              說明:RegexUtils使我們封裝的一個類   isCodeInvalid是裡面的靜態方法,在這個靜態方法裡面又呼叫了另外一個靜態方法得以實現
//          1.2.如果不符合,返回錯誤資訊
            return Result.fail("手機號格式錯誤");
        }
//      2.校驗驗證碼
//           2.1 得到code 這個值是真實的code
        Object cacheCode = session.getAttribute("code");
//           2.2 獲取使用者輸入的code
        String code = loginForm.getCode();
        if(cacheCode ==null || !cacheCode.toString().equals(code)){
//      3.不一致,報錯
            return Result.fail("驗證碼錯誤");
        }
 
//      4.一致,根據手機號查詢使用者   .one()代表查詢一個  list()代表著查詢多個
        User user =query().eq("phone",loginForm.getPhone()).one();
 
//      5.判斷使用者是否存在
        if(user ==null){
//      6.不存在,建立新使用者並儲存
             user = createUserWithPhone(loginForm.getPhone());
        }
 
//      7.儲存使用者資訊到session中
        session.setAttribute("user",user);
        return Result.ok();
    }
 
    private User createUserWithPhone(String phone) {
//      1.建立使用者
        User user = new User();
        user.setPhone(phone);
//       USER_NICK_NAME_PREFIX其實就是 "user_",這樣寫更有逼格
        user.setNickName(USER_NICK_NAME_PREFIX+RandomUtil.randomString(10));
//        儲存使用者
        save(user);
 
        return user;
    }

3.3 攔截器——登入驗證功能

// HandlerInterceptor 這是一個攔截器
public class LoginInterceptor implements HandlerInterceptor {
//  前置攔截   在進入controller之前我們進行登入校驗
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//      1.獲取session
        HttpSession session  =request.getSession();
//      2.獲取session中的使用者
        Object user = session.getAttribute("user");
//      3.判斷使用者是否存在
        if(user == null){
            // 4.不存在,攔截
            response.setStatus(401);  //返回401狀態碼
            return false;
        }
//      5.存在,儲存使用者資訊到ThreadLocal  儲存在當前執行緒裡面的
        UserHolder.saveUser((User)user);
//      6.放行
        return true;
    }
//  在controller執行之後攔截  這個我們在這裡不需要
//    @Override
//    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
//        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
//    }
 
//  渲染之後,返回給使用者之前   使用者業務執行完畢我們要銷燬維護資訊,避免洩露
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//      移除使用者
        UserHolder.removeUser();
    }
}
public class UserHolder {
    private static final ThreadLocal<User> tl = new ThreadLocal<>();
 
    public static void saveUser(User user){
        tl.set(user);
    }
 
    public static User getUser(){
        return tl.get();
    }
 
    public static void removeUser(){
        tl.remove();
    }
}
@Configuration
public class MvcConfig implements WebMvcConfigurer {
 
//  攔截器的註冊器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .excludePathPatterns(
                        "/user/code",
                        "/user/login",
                        "/shop/**",
                        "/blog/hot",
                        "/shop-type/**",
                        "upload/**",
                        "voucher/**"
                );
    }
}
    @GetMapping("/me")
    public Result me(){
        // TODO 獲取當前登入的使用者並返回
//        直接取就可以了
       User user=  UserHolder.getUser();
       
        return Result.ok(user);
    }

三、隱藏使用者敏感資訊

如下圖所示,伺服器返回的資訊有點多,我們為了保護使用者的資訊,我們需要隱藏部分的內容

所以一開始我們存入session的資訊就不應該是完整的資訊,這樣才能降低伺服器的壓力

UserServiceImpl中的login方法

//      7.儲存使用者資訊到session中   
//         BeanUtil.copyProperties(user, UserDTO.class))  會自動的將user中的屬性拷貝到UserDTO當中而且也建立出一個UserDTO物件
        session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));

取的時候我們也應該做出變化

LoginInterceptor類

//      5.存在,儲存使用者資訊到ThreadLocal  儲存在當前執行緒裡面的
        UserHolder.saveUser((UserDTO)user);

此時我們再登入查詢資訊,就還剩下三個欄位了

四、session共用問題

多臺Tomcat並不共用session儲存空間,當請求切換到不同的Tomcat服務導致資料丟失的問題

所以這個方案就被pass了

session的替代方案應該滿足:

資料共用記憶體儲存key、value結構

所以我們選擇Redis

任何一臺Tomcat都能存取到Redis,這樣就能實現資料共用

總結

到此這篇關於如何基於Session實現簡訊登入功能的文章就介紹到這了,更多相關Session簡訊登入內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com