<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
黑馬點評專案主要包括以下功能:
這一章主要介紹簡訊登入功能,簡訊登入功能是基於Redis的共用session實現的
需要專案資料的私信我
其中的表有:
注意:Mysql的版本採用5.7及以上版本
3.1 將後端專案匯入到 Idea 中
3.2 注意:修改application.yaml檔案中的mysql、redis地址資訊 將mysql、redis地址資訊修改為自己的資訊
3.3 啟動專案 啟動專案後,在瀏覽器存取:http://localhost:8081/shop-type/list ,如果可以看到資料則證明執行沒有問題
4.1 匯入nginx資料夾 將nginx資料夾複製到任意目錄,要確保該目錄不包含中文、特殊字元和空格,例如:
4.2 執行前端專案 在nginx所在目錄下開啟一個CMD視窗,輸入命令啟動nginx:
start nginx.exe
開啟chrome瀏覽器,在空白頁面點選滑鼠右鍵,選擇檢查,即可開啟開發者工具:
然後存取: http://127.0.0.1:8080 ,即可看到頁面:
主要程式碼:
@Slf4j @RestController @RequestMapping("/user") public class UserController { @Resource private IUserService userService; /** * 傳送手機驗證碼 */ @PostMapping("code") public Result sendCode(@RequestParam("phone") String phone, HttpSession session) { // 傳送簡訊驗證碼並儲存驗證碼 return userService.sendCode(phone, session); } }
@Service @Slf4j public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService { @Override public Result sendCode(String phone, HttpSession session) { // 1.使用工具類校驗手機號 if (RegexUtils.isPhoneInvalid(phone)) { // 2.如果不符合,返回錯誤資訊 return Result.fail("手機號格式錯誤!"); } // 3.符合,生成驗證碼 String code = RandomUtil.randomNumbers(6); // 4.儲存驗證碼到 session session.setAttribute("code",code); // 5.模擬傳送驗證碼 log.debug("傳送簡訊驗證碼成功,驗證碼:{}", code); // 返回ok return Result.ok(); } }
主要程式碼:
UserController
/** * 登入功能 * @param loginForm 登入引數,包含手機號、驗證碼;或者手機號、密碼 */ @PostMapping("/login") public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){ // 實現登入功能 return userService.login(loginForm, session); }
UserServiceImpl
@Override public Result login(LoginFormDTO loginForm, HttpSession session) { // 1.校驗手機號 String phone = loginForm.getPhone(); if (RegexUtils.isPhoneInvalid(phone)) { // 如果不符合,返回錯誤資訊 return Result.fail("手機號格式錯誤!"); } // 2.校驗驗證碼 Object cacheCode = session.getAttribute("code"); String code = loginForm.getCode(); if (cacheCode == null || !cacheCode.toString().equals(code)) { // 3.驗證碼不一致,則報錯 return Result.fail("驗證碼錯誤"); } // 4.驗證碼一致,根據手機號查詢使用者 User user = query().eq("phone", phone).one(); // 5.判斷使用者是否存在 if (user == null) { // 6.使用者不存在,則建立使用者並儲存 user = createUserWithPhone(phone); } // 7.儲存使用者資訊到session中,UserDTO只包含簡單的使用者資訊, // 而不是完整的User,這樣可以隱藏使用者的敏感資訊(例如:密碼等),還能減少記憶體使用 session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class)); // 8.返回ok return Result.ok(); } private User createUserWithPhone(String phone) { // 1.建立使用者 User user = new User(); user.setPhone(phone); // 隨機設定暱稱 user_mrkuw05lok user.setNickName(SystemConstants.USER_NICK_NAME_PREFIX + RandomUtil.randomString(10)); // 2.儲存使用者 save(user); return user; }
使用者請求登入時,會攜帶cookie,cookie中包含JSEESIONID
為了避免使用者請求每個controller時,每次都去校驗使用者資訊,所以可以加攔截器
攔截器只需在使用者請求存取時,校驗一次後將使用者資訊儲存到ThreadLocal中,供後續執行緒使用
主要程式碼:
在工具類中編寫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(); } }
在工具類中編寫登入攔截器
public class LoginInterceptor implements HandlerInterceptor { /** * 前置攔截 * @param request * @param response * @param handler * @return * @throws Exception */ @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.不存在,攔截,返回401狀態碼 response.setStatus(401); return false; } // 5.存在,儲存使用者資訊到ThreadLocal UserHolder.saveUser((User)user); // 6.放行 return true; } /** * 後置攔截器 * @param request * @param response * @param handler * @param ex * @throws Exception */ @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // 請求結束後移除使用者,防止ThreadLocal造成記憶體漏失 UserHolder.removeUser(); } }
在設定類中新增攔截器設定類
@Configuration public class MvcConfig implements WebMvcConfigurer { /** * 新增攔截器 * @param registry */ @Override public void addInterceptors(InterceptorRegistry registry) { // 登入攔截器 registry.addInterceptor(new LoginInterceptor()) // 排除不需要攔截的路徑 .excludePathPatterns( "/shop/**", "/voucher/**", "/shop-type/**", "/upload/**", "/blog/hot", "/user/code", "/user/login" ); } }
UserController
@GetMapping("/me") public Result me(){ // 獲取當前登入的使用者並返回 UserDTO user = UserHolder.getUser(); return Result.ok(user); }
token作為key,Hash型別的使用者資訊作為value
後端校驗成功後,會返回token給前端,前端會將token儲存到sessionStorage中(這是瀏覽器的儲存方式),以後前端每次請求都會攜帶token,方便後端通過Redis校驗使用者資訊
前端程式碼:將後端返回的token儲存到sessionStorage中
前端每次請求時,都會通過攔截器將token設定到請求頭中,賦值給變數authorization,後端通過authorization獲取前端攜帶的token進行校驗
修改之前程式碼,將驗證碼存入Redis
@Service @Slf4j public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService { @Resource private StringRedisTemplate stringRedisTemplate; @Override public Result sendCode(String phone, HttpSession session) { // 1.使用工具類校驗手機號 if (RegexUtils.isPhoneInvalid(phone)) { // 2.如果不符合,返回錯誤資訊 return Result.fail("手機號格式錯誤!"); } // 3.符合,生成驗證碼 String code = RandomUtil.randomNumbers(6); // 4.儲存驗證碼到 session // session.setAttribute("code",code); // 4.儲存驗證碼到 redis // "login:code:"是業務字首,以"login:code:" + 手機號為key,過期時間2分鐘 stringRedisTemplate.opsForValue().set(RedisConstants.LOGIN_CODE_KEY + phone, code, RedisConstants.LOGIN_CODE_TTL, TimeUnit.MINUTES); // 5.模擬傳送驗證碼 log.debug("傳送簡訊驗證碼成功,驗證碼:{}", code); // 返回ok return Result.ok(); } }
@Override public Result login(LoginFormDTO loginForm, HttpSession session) { // 1.校驗手機號 String phone = loginForm.getPhone(); if (RegexUtils.isPhoneInvalid(phone)) { // 如果不符合,返回錯誤資訊 return Result.fail("手機號格式錯誤!"); } // // 2.校驗驗證碼 // Object cacheCode = session.getAttribute("code"); // String code = loginForm.getCode(); // if (cacheCode == null || !cacheCode.toString().equals(code)) { // // 3.驗證碼不一致,則報錯 // return Result.fail("驗證碼錯誤"); // } // 2.從Redis獲取驗證碼並校驗 String cacheCode = stringRedisTemplate.opsForValue().get(RedisConstants.LOGIN_CODE_KEY + phone); String code = loginForm.getCode(); if (cacheCode == null || !cacheCode.equals(code)) { // 3.驗證碼不一致,則報錯 return Result.fail("驗證碼錯誤"); } // 4.驗證碼一致,根據手機號查詢使用者 User user = query().eq("phone", phone).one(); // 5.判斷使用者是否存在 if (user == null) { // 6.使用者不存在,則建立使用者並儲存 user = createUserWithPhone(phone); } // // 7.儲存使用者資訊到session中,UserDTO只包含簡單的使用者資訊,而不是完整的User,這樣可以隱藏使用者的敏感資訊(例如:密碼等),還能減少記憶體使用 // session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class)); // 7.儲存使用者資訊到redis中 // 7.1隨機生成token,作為登入令牌 // 使用hutool工具中的UUID,true表示不帶「-」符號的UUID String token = UUID.randomUUID().toString(true); // 7.2將User物件轉為Hash型別進行儲存 UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class); // 由於使用的是stringRedisTemplate,所以存入的value中的值必須都是String型別的 // 但是UserDTO中的id是Long型別的,所以進行物件屬性拷貝時,需要自定義實現轉換規則 Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),CopyOptions.create().setIgnoreNullValue(true).setIgnoreNullValue(true).setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString())); // 7.3存入redis, "login:token:"是業務字首,以 "login:token:" + token作為key stringRedisTemplate.opsForHash().putAll(RedisConstants.LOGIN_USER_KEY + token, userMap); // 7.4設定token有效期,有效期為30分鐘 stringRedisTemplate.expire(RedisConstants.LOGIN_USER_KEY + token, RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES); // 8.返回token return Result.ok(token); } private User createUserWithPhone(String phone) { // 1.建立使用者 User user = new User(); user.setPhone(phone); // 隨機設定暱稱 user_mrkuw05lok user.setNickName(SystemConstants.USER_NICK_NAME_PREFIX + RandomUtil.randomString(10)); // 2.儲存使用者 save(user); return user; }
重新整理token的攔截器程式碼:
public class RefreshTokenInterceptor implements HandlerInterceptor { // 因為LoginInterceptor不是通過Spring進行管理的Bean,所以不能再LoginInterceptor中進行注入StringRedisTemplate // 可以通過構造方法傳入StringRedisTemplate private StringRedisTemplate stringRedisTemplate; public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) { this.stringRedisTemplate = stringRedisTemplate; } /** * 前置攔截 * @param request * @param response * @param handler * @return * @throws Exception */ @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.不存在,攔截,返回401狀態碼 // response.setStatus(401); // return false; // } // // 5.存在,儲存使用者資訊到ThreadLocal // UserHolder.saveUser((UserDTO)user); // // 6.放行 // return true; // 1.獲取請求頭中的token String token = request.getHeader("authorization"); if (StrUtil.isBlank(token)) { // 不存在,則攔截,返回401狀態碼 response.setStatus(401); return false; } // 2.通過token獲取redis中的使用者 Map<Object, Object> userMap = stringRedisTemplate.opsForHash() .entries(RedisConstants.LOGIN_USER_KEY + token); // 3.判斷使用者是否存在 if (userMap.isEmpty()) { // 4.使用者不存在,則攔截,返回401狀態碼 response.setStatus(401); return false; } // 5.將redis中Hash型別資料轉換成UserDTO物件 UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false); // 6.使用者存在,儲存使用者資訊到ThreadLocal UserHolder.saveUser(userDTO); // 7.重新整理token有效期 stringRedisTemplate.expire(RedisConstants.LOGIN_USER_KEY + token, RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES); // 8.放行 return true; } /** * 後置攔截器 * @param request * @param response * @param handler * @param ex * @throws Exception */ @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // 請求結束後移除使用者,防止ThreadLocal造成記憶體漏失 UserHolder.removeUser(); } }
登入攔截器的程式碼:
public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 1.判斷是否需要攔截(ThreadLocal中是否有使用者) if (UserHolder.getUser() == null) { // 沒有,需要攔截,設定狀態碼 response.setStatus(401); // 攔截 return false; } // 有使用者,則放行 return true; } }
@Configuration public class MvcConfig implements WebMvcConfigurer { @Resource private StringRedisTemplate stringRedisTemplate; /** * 新增攔截器 * @param registry */ @Override public void addInterceptors(InterceptorRegistry registry) { // 登入攔截器 registry.addInterceptor(new LoginInterceptor()) // 排除不需要攔截的路徑 .excludePathPatterns( "/shop/**", "/voucher/**", "/shop-type/**", "/upload/**", "/blog/hot", "/user/code", "/user/login" ).order(1); // token重新整理的攔截器,order越小,執行優先順序越高,所以token重新整理的攔截器先執行 registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**") .excludePathPatterns( // RefreshTokenInterceptor攔截器也需要放行"/user/code","/user/login",不然token過期後再重新登入就會一直被攔截 "/user/code", "/user/login") .order(0); } }
到此這篇關於Redis實現簡訊登入的企業實戰的文章就介紹到這了,更多相關Redis 簡訊登入 內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45