Das Dark Horse Review-Projekt umfasst hauptsächlich die folgenden Funktionen:
#🎜🎜 #1. SQL importieren Wenn Sie Projektinformationen benötigen, senden Sie mir eine private NachrichtDie Tabellen sind:# 🎜🎜#
3. Importieren Sie das Back-End-Projekt
3.1 Verbinden Sie das Back-End. Importieren Sie das Projekt in Idea
3.2 Hinweis: Ändern Sie die MySQL- und Redis-Adressinformationen in Öffnen Sie die Datei application.yaml und ändern Sie die MySQL- und Redis-Adressinformationen in Ihre eigenen Informationen. //localhost:8081/shop-type/list im Browser, wenn Sie sehen können Die Daten beweisen, dass beim Ausführen kein Problem besteht
4. Importieren Sie das Front-End-Projekt4.1 Importieren Sie den Nginx-Ordner und kopieren Sie den Nginx-Ordner. Gehen Sie in ein beliebiges Verzeichnis und stellen Sie sicher, dass das Verzeichnis kein Chinesisch, Sonderzeichen und Leerzeichen enthält, zum Beispiel: #🎜🎜 #
4.2 Führen Sie das Front-End-Projekt aus und öffnen Sie eines in dem Verzeichnis, in dem sich Nginx befindet. Geben Sie im CMD-Fenster den Befehl zum Starten von Nginx ein:
start nginx.exe
Öffnen Sie den Chrome-Browser, klicken Sie mit der rechten Maustaste auf die leere Seite und wählen Sie „Inspizieren“, um die Entwicklertools zu öffnen:
Dann besuchen Sie: http://127.0.0.1:8080 und Sie sehen die Seite:
#🎜 🎜#
2 Der Anmeldevorgang basiert auf der Sitzung. und die Sitzungs-ID wird an das Front-End zurückgegeben und im Cookie gespeichert. Die Benutzerinformationen können aus der Sitzung über die Sitzungs-ID abgerufen und in ThreadLocal gespeichert werden In verschiedenen Threads können nach dem Abrufen der Benutzerinformationen unterschiedliche Vorgänge implementiert werden, wodurch eine Rolle bei der Thread-Isolation gespielt wird.Hauptcode:
@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); } }
# 🎜🎜#
Hauptcode: UserController@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(); } }
/** * 登录功能 * @param loginForm 登录参数,包含手机号、验证码;或者手机号、密码 */ @PostMapping("/login") public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){ // 实现登录功能 return userService.login(loginForm, session); }
Wenn der Benutzer eine Anmeldung anfordert, wird ein Cookie übertragen und das Cookie enthält JSEESIONID
@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; }
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(); } }
Interceptor-Konfigurationsklasse in Konfigurationsklasse hinzufügen
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(); } }
@GetMapping("/me") public Result me(){ // 获取当前登录的用户并返回 UserDTO user = UserHolder.getUser(); return Result.ok(user); }
手机号作为key,String类型的验证码作为value
用户登录时正好会提交手机号,方便通过Redis进行校验验证码
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(); } }
修改之前代码,从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; }
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); } }
Das obige ist der detaillierte Inhalt vonAnalyse von Unternehmensbeispielen für die SMS-Anmeldung mit Redis. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!