【黑马点评】(一)短信登录

发布于:2025-07-05 ⋅ 阅读:(11) ⋅ 点赞:(0)
(一):短信登录-导入黑马点评项目

在这里插入图片描述

server:
  port: 8081
spring:
  application:
    name: hmdp
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/hmdp?useSSL=false&serverTimezone=UTC
    username: 你的账号
    password: 你的密码
  redis:
    host: 127.0.0.1
    port: 6379
    password: 你的密码
    lettuce:
      pool:
        max-active: 10
        max-idle: 10
        min-idle: 1
        time-between-eviction-runs: 10s
  jackson:
    default-property-inclusion: non_null # JSON处理时忽略非空字段
mybatis-plus:
  type-aliases-package: com.hmdp.entity # 别名扫描包
logging:
  level:
    com.hmdp: debug

启动:

localhost:8081/shop-type/list

在这里插入图片描述

在这里插入图片描述

(二):短信登录 - 基于session实习短信登录流程

在这里插入图片描述

(三)短信登录 - 发送短信验证码

在这里插入图片描述

controller层

    @Resource
    private IUserService userService;


    /**
     * 发送手机验证码
     */
    @PostMapping("code")
    public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {
        // 发送短信验证码并保存验证码
        return userService.setCode(phone, session);
    }

service层

public interface IUserService extends IService<User> {

    Result setCode(String phone, HttpSession session);
}


@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService{

    @Override
    public Result setCode(String phone, HttpSession session) {
        // 校验手机号
        if(RegexUtils.isPhoneInvalid(phone)){
            return Result.fail("手机号格式错误");
        }

        // 生成验证码
        String code = RandomUtil.randomString(6);
        session.setAttribute("code", code);

        log.debug("发送验证码成功:{}", code);
        return Result.ok(code);
    }
}

前后端联调成功:在这里插入图片描述
apipost接口测试:

在这里插入图片描述

(三)短信登录 - 实现短信验证码登录和注册

在这里插入图片描述
controller

    /**
     * 登录功能
     * @param loginForm 登录参数,包含手机号、验证码;或者手机号、密码
     */
    @PostMapping("/login")
    public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){
        // 实现登录功能
        return userService.login(loginForm, session);
    }

service层

public interface IUserService extends IService<User> {


    Result login(LoginFormDTO loginForm, HttpSession session);
}


    @Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {
        String phone = loginForm.getPhone();
        // 校验手机号
        if(RegexUtils.isPhoneInvalid(phone)){
            return Result.fail("手机号格式错误");
        }

        // 校验验证码
        String code = (String) session.getAttribute("code");
        if(!code.equals(loginForm.getCode()) || code == null){
            return Result.fail("验证码错误");
        }

        // 根据手机号查询用户
//        QueryWrapper<User> wrapper = new QueryWrapper<>();
//        wrapper.eq("phone",loginForm.getPhone());
//        User user = getOne(wrapper);
        User user =  query().eq("phone", phone).one();

        if(user == null){
            // 用户不存在,创建新用户
            user =  createUserWithPhone(phone);

        }
        // 保存用户到session
        session.setAttribute("user", user);
        return Result.ok(user);
    }

    private User createUserWithPhone(String phone) {
        User user = new User();
        user.setPhone(phone);
        user.setNickName("user_" + RandomUtil.randomString(10));
        // 保存用户
        save(user);
        return user;
    }

前后端联调成功 - 但是会又跳转到登录页面

apipost测试

在这里插入图片描述

(四)短信登录 - 实现登录校验拦截器

在这里插入图片描述
在这里插入图片描述
拦截器filter

public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 获取session信息
        HttpSession session = request.getSession();
        // 获取用户信息
        Object user = session.getAttribute("user");
        // 判断用户信息是否存在
        if(user == null){
            response.setStatus(404);
            return false;
        }

        // 存入threadlocal中
        UserHolder.saveUser((User) user);
        // 放行
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        UserHolder.removeUser();
    }
}

mvcconfig注册

@Component
public class MvcConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .excludePathPatterns("/user/code",
                        "/user/login",
                        "/shop/**",
                        "/shoe-type/**",
                        "/blog/hot");

    }
}

threadlocal

public class UserHolder {
    private static final ThreadLocal<User> tl = new ThreadLocal<>();

    public static void saveUser(User user){
        tl.set(user);
    }

    public static User getUser(){
        return tl.get();
    }

    public static void removeUser(){
        tl.remove();
    }
}

获取登录信息接口controller

    @GetMapping("/me")
    public Result me(){
        // 获取当前登录的用户并返回
        User user = UserHolder.getUser();
        return Result.ok(user);
    }
(五)隐藏用户敏感信息
 // 保存用户到session
 // 将user->dto
 UserDTO userDTO = new UserDTO();
 BeanUtil.copyProperties(user, userDTO);
 session.setAttribute("user", userDTO);
    @GetMapping("/me")
    public Result me(){
        // 获取当前登录的用户并返回
        UserDTO user = UserHolder.getUser();
        return Result.ok(user);
    }

在这里插入图片描述

jdbc:mysql://127.0.0.1:3306/hmdp?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true

在这里插入图片描述

(六)集群session共享共享问题

在这里插入图片描述

(七)redis代替session的业务流程

在这里插入图片描述
在这里插入图片描述

(七)基于redis实现短信登录
public class RedisConstants {
    public static final String LOGIN_CODE_KEY = "login:code:";
    public static final Long LOGIN_CODE_TTL = 2L;
    public static final String LOGIN_USER_KEY = "login:token:";
    public static final Long LOGIN_USER_TTL = 36000L;

    public static final Long CACHE_NULL_TTL = 2L;

    public static final Long CACHE_SHOP_TTL = 30L;
    public static final String CACHE_SHOP_KEY = "cache:shop:";

    public static final String LOCK_SHOP_KEY = "lock:shop:";
    public static final Long LOCK_SHOP_TTL = 10L;

    public static final String SECKILL_STOCK_KEY = "seckill:stock:";
    public static final String BLOG_LIKED_KEY = "blog:liked:";
    public static final String FEED_KEY = "feed:";
    public static final String SHOP_GEO_KEY = "shop:geo:";
    public static final String USER_SIGN_KEY = "sign:";
}

保存验证码到redis

    @Override
    public Result setCode(String phone, HttpSession session) {
        // 校验手机号
        if(RegexUtils.isPhoneInvalid(phone)){
            return Result.fail("手机号格式错误");
        }

        // 生成验证码
        String code = RandomUtil.randomString(6);
        session.setAttribute("code", code);

        log.debug("发送验证码成功:{}", code);
        // 保存到redis == set key val ex 120
        stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone, code, LOGIN_CODE_TTL, TimeUnit.MINUTES);
        return Result.ok(code);
    }

登录逻辑

     @Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {
        String phone = loginForm.getPhone();
        // 校验手机号
        if(RegexUtils.isPhoneInvalid(phone)){
            return Result.fail("手机号格式错误");
        }

        // 校验验证码
        String cacheCode = (String) stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY);
        String code = loginForm.getCode();
        if(!cacheCode.equals(code) || cacheCode == null){
            return Result.fail("验证码错误");
        }

        User user =  query().eq("phone", phone).one();

        if(user == null){
            // 用户不存在,创建新用户
            user =  createUserWithPhone(phone);

        }

        // 生成token,作为登录令牌
        String token = UUID.randomUUID().toString(true);
        // 将user对象转变为hashMap存储
        UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
        Map<String, Object> userMap = BeanUtil.beanToMap(userDTO);
        // 存储
        String tokenKey = LOGIN_USER_KEY + token;
        stringRedisTemplate.opsForHash().putAll(tokenKey + token, userMap);
        // 设置有效期
        stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES);
        // 返回token
        return Result.ok(token);
    }
    }

类型转换错误

  // 将user对象转变为hashMap存储
  UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
  Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),
          CopyOptions.create()
                  .setIgnoreNullValue(true)
                  .setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));

RedisTemplate
默认使用 JDK 序列化 (JdkSerializationRedisSerializer),将 Java 对象转为二进制存储。

StringRedisTemplate
使用字符串序列化 (StringRedisSerializer),直接存储 UTF-8 字符串。

(八)解决状态登录刷新的问题

在这里插入图片描述
在一层拦截器的基础上,再加一层拦截器专门拦截一切路径做刷新token处理。

因为如果用户在一些不需要拦截的比如首页浏览,不会刷新token的时长。

@Component
public class MvcConfig implements WebMvcConfigurer {

    @Resource
    private  StringRedisTemplate stringRedisTemplate;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .excludePathPatterns("/user/code",
                        "/user/login",
                        "/shop/**",
                        "/shoe-type/**",
                        "/blog/hot").order(1);
        registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate))
                .addPathPatterns("/**").order(0);
    }
}

public class RefreshTokenInterceptor implements HandlerInterceptor {

    private StringRedisTemplate stringRedisTemplate;

    public RefreshTokenInterceptor(StringRedisTemplate RedisTemplate){
        this.stringRedisTemplate = stringRedisTemplate;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 请求头获取token
        String token = request.getHeader("authorization");
        if(StrUtil.isBlank(token)){
            return true;
        }
        // 基于token获取用户信息
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash().
                entries(RedisConstants.LOGIN_USER_KEY + token);

        // 判断用户信息是否存在
        if(userMap.isEmpty()){
            return true;
        }
        // 将hash转为map
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);

        // 存入threadlocal中
        UserHolder.saveUser(userDTO);

        // 刷新token的有效期
        stringRedisTemplate.expire(RedisConstants.LOGIN_USER_KEY + token,LOGIN_USER_TTL, TimeUnit.MINUTES);
        // 放行
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        UserHolder.removeUser();
    }
}

public class LoginInterceptor implements HandlerInterceptor {


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 判断是否需要拦截
        if(UserHolder.getUser() == null){
            response.setStatus(401);
            return false;
        }
        // 放行
        return true;
    }


}

网站公告

今日签到

点亮在社区的每一天
去签到