一、導入黑馬點評項目
黑馬點評項目主要包含以下功能:
1. 導入SQL
需要項目資料的私訊我
其中的表格有:
tb_user:使用者表
- ##tb_user_info:使用者詳情表
- tb_shop:商家資訊表
- tb_shop_type:商家類型表
- tb_blog:使用者日記表(達人探店日記)
- tb_follow:用戶關注表格
- tb_voucher:優惠券表
##tb_voucher_order:優惠券的訂單表
注意:Mysql的版本採用5.7以上版本
## 4. 匯入前端專案
4.1 匯入nginx資料夾將nginx資料夾複製到任意目錄,要確保目錄不包含中文、特殊字元和空格,例如:
-
#4.2 執行前端專案在nginx所在目錄下開啟一個CMD窗口,輸入指令啟動nginx:
start nginx.exe
- 開啟chrome瀏覽器,在空白頁點選滑鼠右鍵,選擇檢查,即可開啟開發者工具:
- 然後造訪: http://127.0.0.1:8080 ,即可看到頁面:
二、基於Session實作登入流程
#後端將產生的驗證碼與使用者資訊儲存到session中,並將sessionId傳回給前端儲存到cookie中
使用者登入時,會攜帶cookie向後端發起請求,後端進行校驗時,從cookie取得sessionId,透過sessionId可以從session中獲取用戶資訊並保存到ThreadLocal中
後續每個線程都有一個ThreadLocal中的用戶副本信息,不同線程拿到用戶資訊後可以實現不同的操作,從而起到線程隔離作用
1. 發送簡訊驗證碼
@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(); } }
2. 簡訊驗證碼登入、註冊
# 主程式碼:
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; }
3. 登入驗證功能
使用者要求登入時,會攜帶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); }
三、集群的session共享问题
四、基于Redis实现共享session的登录功能
1. 选择合适的数据结构存入Redis
手机号作为key,String类型的验证码作为value
用户登录时正好会提交手机号,方便通过Redis进行校验验证码
token作为key,Hash类型的用户信息作为value
后端校验成功后,会返回token给前端,前端会将token保存到sessionStorage中(这是浏览器的存储方式),以后前端每次请求都会携带token,方便后端通过Redis校验用户信息
前端代码:将后端返回的token保存到sessionStorage中
前端每次请求时,都会通过拦截器将token设置到请求头中,赋值给变量authorization,后端通过authorization获取前端携带的token进行校验
2. 发送短信验证码
修改之前代码,将验证码存入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(); } }
3. 短信验证码登录、注册
修改之前代码,从Redis获取验证码并校验
随机生成token,保存用户信息到redis中,返回token
@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; }
4. 解决token刷新问题
token刷新问题是指,用户长时间不进行界面操作时,到了过期时间,token自动失效;但是,用户一旦进行操作,就需要给token续期,即更新token过期时间
为了解决token刷新问题,需要加2个拦截器
第一个拦截器可以拦截所有请求,只要用户有请求就刷新token,并保存用户信息到ThreadLocal中
第二个拦截器只对登录请求进行拦截,从ThreadLocal中获取用户信息进行校验
刷新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實作簡訊登入的企業實例分析的詳細內容。更多資訊請關注PHP中文網其他相關文章!

redisisbothadatabaseandaserver.1)asadatabase,ituseSin-memorystorageforfastaccess,ifealforreal-timeapplications andCaching.2)Asaserver,ItsupportsPub/submessagingAndluAsessingandluAsessingandluascriptingftingftingftingftingftingftingftingfinteral-timecommunicationandserverserverserverserverserverserverserver-soperations。

Redis是NoSQL數據庫,提供高性能和靈活性。 1)通過鍵值對存儲數據,適合處理大規模數據和高並發。 2)內存存儲和單線程模型確保快速讀寫和原子性。 3)使用RDB和AOF機制進行數據持久化,支持高可用性和橫向擴展。

Redis是一种内存数据结构存储系统,主要用作数据库、缓存和消息代理。它的核心特点包括单线程模型、I/O多路复用、持久化机制、复制与集群功能。Redis在实际应用中常用于缓存、会话存储和消息队列,通过选择合适的数据结构、使用管道和事务、以及进行监控和调优,可以显著提升其性能。

Redis和SQL數據庫的主要區別在於:Redis是內存數據庫,適用於高性能和靈活性需求;SQL數據庫是關係型數據庫,適用於復雜查詢和數據一致性需求。具體來說,1)Redis提供高速數據訪問和緩存服務,支持多種數據類型,適用於緩存和實時數據處理;2)SQL數據庫通過表格結構管理數據,支持複雜查詢和事務處理,適用於電商和金融系統等需要數據一致性的場景。

REDISACTSASBOTHADATASTOREANDASERVICE.1)ASADATASTORE,ITUSESIN-MEMORYSTOOGATOFORFOFFASTESITION,支持VariousDatharptructuresLikeKey-valuepairsandsortedsetsetsetsetsetsetsets.2)asaservice,ItprovidespunctionslikeItionitionslikepunikeLikePublikePublikePlikePlikePlikeAndluikeAndluAascriptingiationsmpleplepleclexplectiations

Redis與其他數據庫相比,具有以下獨特優勢:1)速度極快,讀寫操作通常在微秒級別;2)支持豐富的數據結構和操作;3)靈活的使用場景,如緩存、計數器和發布訂閱。選擇Redis還是其他數據庫需根據具體需求和場景,Redis在高性能、低延遲應用中表現出色。

Redis在數據存儲和管理中扮演著關鍵角色,通過其多種數據結構和持久化機製成為現代應用的核心。 1)Redis支持字符串、列表、集合、有序集合和哈希表等數據結構,適用於緩存和復雜業務邏輯。 2)通過RDB和AOF兩種持久化方式,Redis確保數據的可靠存儲和快速恢復。

Redis是一種NoSQL數據庫,適用於大規模數據的高效存儲和訪問。 1.Redis是開源的內存數據結構存儲系統,支持多種數據結構。 2.它提供極快的讀寫速度,適合緩存、會話管理等。 3.Redis支持持久化,通過RDB和AOF方式確保數據安全。 4.使用示例包括基本的鍵值對操作和高級的集合去重功能。 5.常見錯誤包括連接問題、數據類型不匹配和內存溢出,需注意調試。 6.性能優化建議包括選擇合適的數據結構和設置內存淘汰策略。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

MantisBT
Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。

Atom編輯器mac版下載
最受歡迎的的開源編輯器

VSCode Windows 64位元 下載
微軟推出的免費、功能強大的一款IDE編輯器

記事本++7.3.1
好用且免費的程式碼編輯器

EditPlus 中文破解版
體積小,語法高亮,不支援程式碼提示功能