我們項(xiàng)目開(kāi)發(fā)中比較通用的登錄方式是用賬號(hào)密碼登錄,但實(shí)際生活中短信登錄的方式是大家所常用的。
下面的文章,前三步我先進(jìn)行了短信登錄的實(shí)現(xiàn),最后一步對(duì)其進(jìn)行了異常處理封裝的細(xì)節(jié)優(yōu)化。
實(shí)現(xiàn)驗(yàn)證碼登錄流程分析 1)發(fā)送驗(yàn)證碼 用戶在提交手機(jī)號(hào)后,會(huì)校驗(yàn)手機(jī)號(hào)是否合法,如果不合法,則要求用戶重新輸入手機(jī)號(hào)。
如果手機(jī)號(hào)合法,后臺(tái)此時(shí)生成對(duì)應(yīng)的驗(yàn)證碼,同時(shí)將驗(yàn)證碼進(jìn)行保存,然后再通過(guò)短信的方式將驗(yàn)證碼發(fā)送給用戶。
2)短信驗(yàn)證碼登錄、注冊(cè) 用戶需輸入收到的驗(yàn)證碼及關(guān)聯(lián)的手機(jī)號(hào)碼。服務(wù)器后臺(tái)會(huì)從當(dāng)前的Session中提取先前存儲(chǔ)的驗(yàn)證碼,并與用戶所輸入的進(jìn)行比對(duì)。若兩者不匹配,用戶將無(wú)法完成驗(yàn)證過(guò)程。若驗(yàn)證碼相符,系統(tǒng)將依據(jù)用戶輸入的手機(jī)號(hào)碼在數(shù)據(jù)庫(kù)中進(jìn)行用戶查詢。若查詢結(jié)果顯示該手機(jī)號(hào)尚未注冊(cè),系統(tǒng)將自動(dòng)創(chuàng)建新的用戶賬戶,并將基礎(chǔ)信息存入數(shù)據(jù)庫(kù)。無(wú)論用戶是已存在或是新注冊(cè),系統(tǒng)都會(huì)更新 Session 信息,保存用戶的登錄憑證,以便用戶能夠順利進(jìn)行后續(xù)操作并隨時(shí)訪問(wèn)其賬戶信息。
代碼實(shí)現(xiàn)驗(yàn)證碼發(fā)送 這里我使用 MyBatisX 實(shí)現(xiàn)項(xiàng)目的初始化。
1)正則表達(dá)式類 使用正則表達(dá)式分別對(duì)手機(jī)號(hào)、密碼、驗(yàn)證碼進(jìn)行校驗(yàn)。
正則表達(dá)式可以去網(wǎng)上找,我這里提供下我的實(shí)現(xiàn)方式,可以作為參考。
public class RegexPatterns { /** * 手機(jī)號(hào)正則 */ public static final String PHONE_REGEX="1\\d{10}" ; /** * 郵箱正則 */ public static final String EMAIL_REGEX="/^([a-z0-9_\\.-]+)@([\\da-z\\.-]+)\\.([a-z\\.]{2,6})$/" ; /** * 驗(yàn)證碼正則 */ public static final String VERIFY_CODE_REGEX="^[a-zA-Z\\d]{6}$" ; }
2)正則校驗(yàn)工具類 對(duì) Controller 層傳入的手機(jī)號(hào)進(jìn)行校驗(yàn)。
滿足手機(jī)號(hào)正則表達(dá)式,手機(jī)號(hào) 11 位,并且只能為數(shù)字,才能校驗(yàn)通過(guò) 。
這里我使用的是 Hutool 工具類來(lái)校驗(yàn)手機(jī)號(hào)和驗(yàn)證碼是否合法。
我貼一下 Hutool 的官網(wǎng),有需要更加深層次了解的小伙伴可以訪問(wèn)下哦!
Hutool 官網(wǎng):https://doc.hutool.cn/pages/index/
public class RegexUtils { /** * 校驗(yàn)手機(jī)號(hào)是否合法 * @param phone * @return */ public static boolean isPhoneInvalid(String phone){ boolean matches = phone.matches(RegexPatterns.PHONE_REGEX); return matches; } /** * 校驗(yàn)驗(yàn)證碼是否合法 * @param code * @return */ public boolean isCodeInvalid(String code){ boolean matches = code.matches(RegexPatterns.VERIFY_CODE_REGEX); return matches; } }
3)Controller 層 @GetMapping("/code" ) public boolean SendCode(String phone, HttpSession session){ boolean b = userService.sendCode(phone, session); return b; }
4)Service 層 首先,系統(tǒng)會(huì)將用戶輸入的手機(jī)號(hào)通過(guò)正則表達(dá)式校驗(yàn)工具類進(jìn)行驗(yàn)證,確保其格式正確無(wú)誤。一旦手機(jī)號(hào)通過(guò)校驗(yàn),程序便會(huì)自動(dòng)生成一個(gè)隨機(jī)驗(yàn)證碼,并將其安全地存儲(chǔ)在服務(wù)器的 Session 中。為了便于開(kāi)發(fā)者實(shí)時(shí)監(jiān)控驗(yàn)證碼的生成和發(fā)送狀態(tài),系統(tǒng)特別設(shè)計(jì)了在控制臺(tái)以 debug 模式輸出驗(yàn)證碼的功能,從而確保了整個(gè)驗(yàn)證流程的透明度和可追蹤性。
@Resource private UserService userService; public boolean sendCode(String phone, HttpSession session) { //1、校驗(yàn)手機(jī)號(hào)是否合法 if (!RegexUtils.isPhoneInvalid(phone)) { return false ; } //2、生成隨機(jī)驗(yàn)證嗎 String code = RandomUtil.randomNumbers(6); //3、保存驗(yàn)證碼 session.setAttribute("code" ,code); //4、打印日志 log.debug("發(fā)送短信驗(yàn)證碼成功,驗(yàn)證碼:{}" ,code); return true ; }
注意:這里需要開(kāi)啟 debug 日志
controller 層中加入@Slf4j
注解
logging: level: com.example: debug# 開(kāi)啟debug日志
結(jié)果:
實(shí)現(xiàn)驗(yàn)證碼登錄注冊(cè) 短信驗(yàn)證登錄注冊(cè)邏輯:
1、校驗(yàn)手機(jī)號(hào)
2、校驗(yàn)驗(yàn)證碼(取出 Session 中保存的驗(yàn)證碼與表單中的輸入的驗(yàn)證碼嗎進(jìn)行比較)
3、不一致:報(bào)錯(cuò)
4、一致:根據(jù)手機(jī)號(hào)查詢用戶
5、判斷用戶是否存在
6、不存在,根據(jù)手機(jī)號(hào)創(chuàng)建新用戶并保存
用戶是憑空創(chuàng)建的,所以密碼可以沒(méi)有,
7、保存用戶信息到 Session 中
1)Controller 層 我們登錄需要獲取兩個(gè)參數(shù),用戶名和手機(jī)號(hào),根據(jù)手機(jī)號(hào)來(lái)創(chuàng)建用戶名。
@PostMapping("/login" ) public boolean login(LoginFormDTO loginFormDTO, HttpSession session){ boolean login = userService.Login(loginFormDTO, session); return login; }
為了使代碼更加美觀,創(chuàng)建一個(gè)參數(shù)封裝類。
/** * 用戶登錄請(qǐng)求參數(shù)封裝類 */ @Data public class LoginFormDTO { private String code; private String phone; }
2)Servcie 層 /** * 用戶登錄 * @param loginFormDTO * @param session * @return */ @Override public boolean Login(LoginFormDTO loginFormDTO, HttpSession session) { //1、首先校驗(yàn)手機(jī)號(hào)和驗(yàn)證碼是否合法 String phone = loginFormDTO.getPhone(); if (!RegexUtils.isPhoneInvalid(phone)){ return false ; } //2、校驗(yàn)驗(yàn)證碼 Object cachecode = session.getAttribute("code" ); String dtoCode = loginFormDTO.getCode(); if (dtoCode==null&&!dtoCode.equals(cachecode)) { return false ; } //3、根據(jù)手機(jī)號(hào)查詢用戶信息 QueryWrapper<User> queryWrapper=new QueryWrapper<User>(); queryWrapper.eq("phone" , phone); //4、根據(jù)查詢條件查詢數(shù)據(jù)庫(kù)中滿足以上條件的用戶 User user = userMapper.selectOne(queryWrapper); if (user==null) { //創(chuàng)建用戶 user=CreateUser(phone); } //5、保存用戶信息到session中 session.setAttribute("user" ,user); return true ; }
將創(chuàng)建用戶的這段代碼單獨(dú)封裝為一個(gè)函數(shù)。
/** * 創(chuàng)建用戶 * @param phone * @return */ private User CreateUser(String phone){ User user = new User(); user.setPhone(phone); user.setNickName(RandomUtil.randomString(10)); //保存用戶 save(user); return user; }
發(fā)送驗(yàn)證碼
get http://localhost:8080/api/user/code?phone=13177576913
校驗(yàn)驗(yàn)證碼并登錄
post http://localhost:8080/api/user/login?phone=13177576913 &code=686422
運(yùn)行結(jié)果:
優(yōu)化:全局通用返回對(duì)象 前面我的測(cè)試前端返回的數(shù)據(jù)都太過(guò)單調(diào),因?yàn)檫@是一個(gè)功能的實(shí)現(xiàn),并不是做一個(gè)完整的項(xiàng)目,但這樣看起來(lái)確實(shí)不太美觀。
所以這里使用 ==》全局統(tǒng)一 API 響應(yīng)框架 ==》對(duì)整個(gè)接口進(jìn)行統(tǒng)一的異常處理封裝,這樣就不需要寫那么多的異常處理類了。
1)引入依賴 使用rest-api-spring-boot-starter
這個(gè)依賴,不用打開(kāi) Redis ,但這個(gè)依賴需要加上。
<!--RestfulAPI--> <dependency> <groupId>cn.soboys</groupId> <artifactId>rest-api-spring-boot-starter</artifactId> <version>1.3.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
2)注解 啟動(dòng)類上加上這個(gè)@EnableRestFullApi
注解。
只需要引入依賴和加上注解這兩步,Log 日志就變得與上面沒(méi)優(yōu)化前的不一樣了。
注意:這里的端口號(hào)是 8000,也就是說(shuō)使用這個(gè)依賴必須要 8000 端口,我們?cè)谂渲梦募兴O(shè)置的 Web 端口沒(méi)有用了,無(wú)法自定義端口
運(yùn)行后,通過(guò) Postman 測(cè)試,測(cè)試結(jié)果如下圖所示:
3)用戶信息脫敏 我們這里進(jìn)行用戶脫敏,將返回對(duì)象單獨(dú)封裝。
@Data public class UserDTO { private Long id; private String nickName; private String icon; }
修改登錄login
方法中的下面所示內(nèi)容。
/** * copyProperties:屬性拷貝——把user中的屬性字動(dòng)拷貝到UserDTO中 * BeanUtils:使用的是包c(diǎn)n.hutool.core.bean下的工具類 */ //5、保存用戶信息到session中 UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class); session.setAttribute("user" , userDTO);
最終,如下圖所示成功返回三個(gè)信息。
以上,就是今天的分享,希望對(duì)大家有幫助。
該文章在 2024/4/12 23:15:39 編輯過(guò)