?🌈hello,你好鴨,我是Ethan,一名不斷學習的碼農,很高興你能來閱讀。
??目前博客主要更新Java系列、項目案例、計算機必學四件套等。
🏃人生之義,在于追求,不在成敗,勤通大道。加油呀!
🔥個人主頁:Ethan Yankang
🔥專欄:黑馬點評||Java項目
🔥本篇概覽:講解基于session的短信登錄。Threadlocal的使用。雙攔截器的使用。數據脫敏的技巧等。
目錄
????????????1、短信登錄——基于session
1.1、導入黑馬點評項目
1.1.1 、導入SQL
1.1.2、有關當前模型
nginx總結:
1.1.3、導入后端項目
1.1.4、導入前端工程
1.1.5 運行前端項目
1.2 、基于Session實現登錄流程
知識補充:session
1.3 、實現發送短信驗證碼功能
? 代碼——發送驗證碼
? 代碼——登錄
1.4、實現登錄攔截功能
溫馨小貼士:tomcat的運行原理
溫馨小貼士:關于threadlocal
? 代碼——攔截器
? 代碼——讓攔截器生效
1.5、用戶信息脫敏
?代碼——將user對象換成UserDTO
1.6、session共享問題——用redis的原因
1、短信登錄——基于session
1.1、導入黑馬點評項目
1.1.1 、導入SQL
1.1.2、有關當前模型
手機或者app端發起請求,請求我們的nginx服務器,nginx基于七層模型走的是HTTP協議,可以實現基于Lua直接繞開tomcat訪問redis,也可以作為靜態資源服務器,輕松扛下上萬并發, 負載均衡到下游tomcat服務器,打散流量,我們都知道一臺4核8G的tomcat,在優化和處理簡單業務的加持下,大不了就處理1000左右的并發, 經過nginx的負載均衡分流后,利用集群支撐起整個項目,同時nginx在部署了前端項目后,更是可以做到動靜分離,進一步降低tomcat服務的壓力,這些功能都得靠nginx起作用,所以nginx(面試準備)是整個項目中重要的一環。
nginx總結:
動靜分離
靜態部署
負載均衡
在tomcat支撐起并發流量后,我們如果讓tomcat直接去訪問Mysql,根據經驗Mysql企業級服務器只要上點并發,一般是16或32 核心cpu,32 或64G內存,像企業級mysql加上固態硬盤能夠支撐的并發,大概就是4000起~7000左右,上萬并發, 瞬間就會讓Mysql服務器的cpu,硬盤全部打滿,容易崩潰,所以我們在高并發場景下,會選擇使用mysql集群(面試準備),同時為了進一步降低Mysql的壓力,同時增加訪問的性能,我們也會加入Redis,同時使用Redis集群(面試準備)使得Redis對外提供更好的服務。
1.1.3、導入后端項目
在資料中提供了一個項目源碼:
1.1.4、導入前端工程
1.1.5 運行前端項目
1.2 、基于Session實現登錄流程
知識補充:session
在 Web 開發中,Session 指的是服務器為了保存用戶在多次請求之間的狀態信息而創建的一種機制。當用戶訪問網站時,服務器會為該用戶創建一個唯一的 Session 對象,用于存儲用戶相關的特定數據,如登錄狀態、用戶偏好等。在同一次會話(會話通常指從用戶打開瀏覽器開始訪問某個網站到關閉瀏覽器的整個過程。)期間,用戶與服務器之間的交互可以通過這個 Session 來維護相關狀態和數據,使得服務器能夠識別是同一個用戶在進行操作,從而實現個性化的服務和交互。它通常基于 Cookie 或其他機制在客戶端和服務器之間進行關聯和傳遞。
發送驗證碼:
用戶在提交手機號后,會校驗手機號是否合法,如果不合法,則要求用戶重新輸入手機號
如果手機號合法,后臺此時生成對應的驗證碼,同時將驗證碼進行保存,然后再通過短信的方式將驗證碼發送給用戶(實際上必要的知識驗證碼,而非短信)。
短信驗證碼登錄、注冊:
用戶將驗證碼和手機號進行輸入,后臺從session中拿到當前驗證碼,然后和用戶輸入的驗證碼進行校驗。
如果不一致,則無法通過校驗。
如果一致,則后臺根據手機號查詢用戶,如果用戶不存在,則為用戶創建賬號信息,保存到數據庫,無論是否存在,都會將用戶信息保存到session中,方便后續獲得當前登錄信息
校驗登錄狀態:
用戶在請求時候,會從cookie中攜帶著JsessionId到后臺,后臺通過JsessionId從session中拿到用戶信息,如果沒有session信息,則進行攔截,如果有session信息,則將用戶信息保存到threadLocal中,并且放行
1.3 、實現發送短信驗證碼功能
頁面流程
具體代碼如下
貼心小提示:
具體邏輯上文已經分析,我們僅僅只需要按照提示的邏輯寫出代碼即可。
-
? 代碼——發送驗證碼
?@Overridepublic Result sendCode(String phone, HttpSession session) {// 1.校驗手機號if (RegexUtils.isPhoneInvalid(phone)) {// 2.如果不符合,返回錯誤信息return Result.fail("手機號格式錯誤!");}// 3.符合,生成驗證碼String code = RandomUtil.randomNumbers(6);
?// 4.保存驗證碼到 sessionsession.setAttribute("code",code);// 5.發送驗證碼log.debug("發送短信驗證碼成功,驗證碼:{}", code);// 返回okreturn Result.ok();}
-
? 代碼——登錄
?@Overridepublic Result login(LoginFormDTO loginForm, HttpSession session) {// 1.校驗手機號String phone = loginForm.getPhone();if (RegexUtils.isPhoneInvalid(phone)) {// 2.如果不符合,返回錯誤信息return Result.fail("手機號格式錯誤!");}// 3.校驗驗證碼Object cacheCode = session.getAttribute("code");String code = loginForm.getCode();if(cacheCode == null || !cacheCode.toString().equals(code)){//3.不一致,報錯return Result.fail("驗證碼錯誤");}//一致,根據手機號查詢用戶User user = query().eq("phone", phone).one();
?//5.判斷用戶是否存在if(user == null){//不存在,則創建user = ?createUserWithPhone(phone);}//7.保存用戶信息到session中session.setAttribute("user",user);
?return Result.ok();}
1.4、實現登錄攔截功能
溫馨小貼士:tomcat的運行原理
當用戶發起請求時,會訪問我們向tomcat注冊的端口,任何程序想要運行,都需要有一個線程對當前端口號進行監聽,tomcat也不例外,當監聽線程知道用戶想要和tomcat連接連接時,那會由監聽線程創建socket連接,socket都是成對出現的,用戶通過socket像互相傳遞數據,當tomcat端的socket接受到數據后,此時監聽線程會從tomcat的線程池中取出一個線程執行用戶請求,在我們的服務部署到tomcat后,線程會找到用戶想要訪問的工程,然后用這個線程轉發到工程中的controller,service,dao中,并且訪問對應的DB,在用戶執行完請求后,再統一返回,再找到tomcat端的socket,再將數據寫回到用戶端的socket,完成請求和響應
通過以上講解,我們可以得知 每個用戶其實對應都是去找tomcat線程池中的一個線程來完成工作的, 使用完成后再進行回收,既然每個請求都是獨立的,所以在每個用戶去訪問我們的工程時,我們可以使用threadlocal來做到線程隔離,每個線程操作自己的一份數據
溫馨小貼士:關于threadlocal
如果小伙伴們看過threadLocal的源碼,你會發現在threadLocal中,無論是他的put方法和他的get方法, 都是先從獲得當前用戶的線程,然后從線程中取出線程的成員變量map,只要線程不一樣,map就不一樣,所以可以通過這種方式來做到線程隔離
有關Threadlocal詳情帖子請點擊這里
? 代碼——攔截器
public class LoginInterceptor implements HandlerInterceptor {
?@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//1.獲取sessionHttpSession session = request.getSession();//2.獲取session中的用戶Object user = session.getAttribute("user");//3.判斷用戶是否存在if(user == null){//4.不存在,攔截,返回401狀態碼response.setStatus(401);return false;}//5.存在,保存用戶信息到ThreadlocalUserHolder.saveUser((User)user);//6.放行return true;}
}
? 代碼——讓攔截器生效
@Configuration
public class MvcConfig implements WebMvcConfigurer {
?@Resourceprivate StringRedisTemplate stringRedisTemplate;
?@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 登錄攔截器registry.addInterceptor(new LoginInterceptor()).excludePathPatterns("/shop/**","/voucher/**","/shop-type/**","/upload/**","/blog/hot","/user/code","/user/login").order(1);// token刷新的攔截器registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**").order(0);}
}
1.5、用戶信息脫敏
我們通過瀏覽器觀察到此時用戶的全部信息都在,這樣極為不靠譜,所以我們應當在返回用戶信息之前,將用戶的敏感信息進行隱藏,采用的核心思路就是書寫一個UserDto對象,這個UserDto對象就沒有敏感信息了,我們在返回前,將有用戶敏感信息的User對象轉化成沒有敏感信息的UserDto對象,那么就能夠避免這個尷尬的問題了
在登錄方法處修改
//7.保存用戶信息到session中
session.setAttribute("user", BeanUtils.copyProperties(user,UserDTO.class));
在攔截器處:
//5.存在,保存用戶信息到Threadlocal
UserHolder.saveUser((UserDTO) user);
在UserHolder處:
?代碼——將user對象換成UserDTO
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();}
}
1.6、session共享問題——引出用redis的原因
核心思路分析:
每個tomcat中都有一份屬于自己的session,假設用戶第一次訪問第一臺tomcat,并且把自己的信息存放到第一臺服務器的session中,但是第二次這個用戶訪問到了第二臺tomcat,那么在第二臺服務器上,肯定沒有第一臺服務器存放的session,所以此時整個登錄攔截功能就會出現問題,我們能如何解決這個問題呢?早期的方案是session拷貝,就是說雖然每個tomcat上都有不同的session,但是每當任意一臺服務器的session修改時,都會同步給其他的Tomcat服務器的session,這樣的話,就可以實現session的共享了
但是這種方案具有兩個大問題
1、每臺服務器中都有完整的一份session數據,服務器壓力過大。
2、session拷貝數據時,可能會出現延遲
所以咱們后來采用的方案都是基于redis來完成,我們把session換成redis,redis數據本身就是共享的,就可以避免session共享的問題了
📣非常感謝你閱讀到這里,如果這篇文章對你有幫助,希望能留下你的點贊👍 關注? 分享👥 留言💬thanks!!!
📚愿大家都能學有所得,功不唐捐!