如何基于Session實現(xiàn)短信登錄功能
一、基于Session實現(xiàn)登錄
1.1 業(yè)務流程圖

二、發(fā)送短信驗證碼
2.1 發(fā)送短信請求方式及參數(shù)說明

這個地方為什么需要session? 因為我們需要把驗證碼保存在session當中
/**
* 發(fā)送手機驗證碼
*/
@PostMapping("code")
public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {
// TODO 發(fā)送短信驗證碼并保存驗證碼
// return Result.fail("功能未完成");
return userService.sendCode(phone,session);
}2.2 業(yè)務層代碼模擬發(fā)送短信
@Override
public Result sendCode(String phone, HttpSession session) {
// 1.校驗手機號
if(RegexUtils.isPhoneInvalid(phone)){
// 說明:RegexUtils使我們封裝的一個類 isCodeInvalid是里面的靜態(tài)方法,在這個靜態(tài)方法里面又調(diào)用了另外一個靜態(tài)方法得以實現(xiàn)
// 2.如果不符合,返回錯誤信息
return Result.fail("手機號格式錯誤");
}
// 3.符合,生成驗證碼 6代表生成的驗證碼的長度 RandomUtil使用這個工具類生成
String code = RandomUtil.randomNumbers(6);
// 4.保存驗證碼到session key必須是一個字符串,value是一個對象
session.setAttribute("code",code);
// 5.發(fā)送驗證碼
// 實現(xiàn)起來比較麻煩 我們使用日志假裝發(fā)送
log.debug("發(fā)送短信驗證碼成功,驗證碼:"+code);
return Result.ok();
}
}
三、登錄功能
3.1 短信驗證的請求方式及路徑

/**
* 登錄功能
* @param loginForm 登錄參數(shù),包含手機號、驗證碼;或者手機號、密碼
*/
@PostMapping("/login")
public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){
// TODO 實現(xiàn)登錄功能
return userService.login(loginForm,session);
}3.2 業(yè)務層代碼實現(xiàn)用戶登錄
流程圖:

代碼:
/**
* 實現(xiàn)用戶登錄
* @param loginForm 登錄的參數(shù)
* @param session
* @return
*/
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
// 1.校驗手機號
if(RegexUtils.isPhoneInvalid(loginForm.getPhone())){
// 說明:RegexUtils使我們封裝的一個類 isCodeInvalid是里面的靜態(tài)方法,在這個靜態(tài)方法里面又調(diào)用了另外一個靜態(tài)方法得以實現(xiàn)
// 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.一致,根據(jù)手機號查詢用戶 .one()代表查詢一個 list()代表著查詢多個
User user =query().eq("phone",loginForm.getPhone()).one();
// 5.判斷用戶是否存在
if(user ==null){
// 6.不存在,創(chuàng)建新用戶并保存
user = createUserWithPhone(loginForm.getPhone());
}
// 7.保存用戶信息到session中
session.setAttribute("user",user);
return Result.ok();
}
private User createUserWithPhone(String phone) {
// 1.創(chuàng)建用戶
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狀態(tài)碼
return false;
}
// 5.存在,保存用戶信息到ThreadLocal 保存在當前線程里面的
UserHolder.saveUser((User)user);
// 6.放行
return true;
}
// 在controller執(zhí)行之后攔截 這個我們在這里不需要
// @Override
// public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
// }
// 渲染之后,返回給用戶之前 用戶業(yè)務執(zhí)行完畢我們要銷毀維護信息,避免泄露
@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);
}三、隱藏用戶敏感信息
如下圖所示,服務器返回的信息有點多,我們?yōu)榱吮Wo用戶的信息,我們需要隱藏部分的內(nèi)容

所以一開始我們存入session的信息就不應該是完整的信息,這樣才能降低服務器的壓力
UserServiceImpl中的login方法
// 7.保存用戶信息到session中 \
// BeanUtil.copyProperties(user, UserDTO.class)) 會自動的將user中的屬性拷貝到UserDTO當中而且也創(chuàng)建出一個UserDTO對象
session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));取的時候我們也應該做出變化
LoginInterceptor類
// 5.存在,保存用戶信息到ThreadLocal 保存在當前線程里面的
UserHolder.saveUser((UserDTO)user);此時我們再登錄查詢信息,就還剩下三個字段了

四、session共享問題
多臺Tomcat并不共享session存儲空間,當請求切換到不同的Tomcat服務導致數(shù)據(jù)丟失的問題
所以這個方案就被pass了
session的替代方案應該滿足:
數(shù)據(jù)共享內(nèi)存存儲key、value結(jié)構(gòu)
所以我們選擇Redis
任何一臺Tomcat都能訪問到Redis,這樣就能實現(xiàn)數(shù)據(jù)共享
總結(jié)
到此這篇關(guān)于如何基于Session實現(xiàn)短信登錄功能的文章就介紹到這了,更多相關(guān)Session短信登錄內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
關(guān)于Redis數(shù)據(jù)庫三種持久化方案介紹
大家好,本篇文章主要講的是關(guān)于Redis數(shù)據(jù)庫三種持久化方案介紹,感興趣的同學趕快來看一看吧,對你有幫助的話記得收藏一下2022-01-01
redis-cli創(chuàng)建redis集群的實現(xiàn)
本文主要介紹了redis-cli創(chuàng)建redis集群的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2024-06-06
Redisson延時隊列RedissonDelayed的具體使用
定時調(diào)度基本是每個項目都會遇到的業(yè)務場景,一般地,都會通過任務調(diào)度工具執(zhí)行定時任務完成,但是會有一定的缺點,本文主要介紹了Redisson延時隊列RedissonDelayed的具體使用,感興趣的可以了解一下2024-02-02
redis-benchmark并發(fā)壓力測試的問題解析
這篇文章主要介紹了redis-benchmark并發(fā)壓力測試的問題解析,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-01-01
Redis數(shù)據(jù)結(jié)構(gòu)原理淺析
這篇文章主要為大家介紹了Redis數(shù)據(jù)結(jié)構(gòu)原理淺析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-02-02

