黑马点评1——基于session实现登录

流程图

发送短信验证码

在这里插入图片描述
代码逻辑很简单
service层实现

@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. TODO 发送验证码-调用第三方平台发送
        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("验证码错误!");
        }
        // 4. 根据手机号查询用户 select * from tb_user where phone = ?
        User user = query().eq("phone", phone).one();
        // 5. 判断用户是否存在
        if(user == null){
            // 6. 不存在,创建新用户并保存
            user = createUserWithPhone(phone);
        }

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

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

校验登录状态

在这里插入图片描述
这个可以使用拦截器实现,如果用户不存在我们就拦截请求直接返回
定义一个拦截器


public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1. 获取session
        HttpSession session = request.getSession();

        // 2. 获取session中的用户
        User user = (User)session.getAttribute("user");
        // 3. 判断用户是否存在
        if(user == null){
           // 4. 不存在,拦截
            response.setStatus(401);
            return false;
        }

        //5. 存在,保存用户信息到ThreadLocal
        UserHolder.saveUser(user);
        // 6. 放行
        return true;
    }

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

把拦截器配置进去

package com.hmdp.config;

import com.hmdp.utils.LoginInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class MvcConfig implements WebMvcConfigurer {

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

集群的Session共享问题

在这里插入图片描述
解决方案就是让session共享, 那可以共享吗?早期可以session拷贝,但可行吗?
有问题的:
第一:拷贝有延迟, 会有数据不一致的问题
第二:互相拷来拷去,相同的数据,浪费内存空间
所以,该方案pass

那么替代session方案应该满足:

  • 数据共享
  • 内存存储
  • key、value结构
    那答案就是redis

基于Redis实现共享session登录

在这里插入图片描述

在这里插入图片描述
我们保存的用户信息的方式选择hash的方式
那登录校验怎么做?
因为用token作为key存储用户信息,现在用户得使用token作为用户的登录凭证,但是tomcat不会把token写到浏览器,所以,我只能手动把token返回客户端,客户端把token保存下来,每次请求携带token。就变成了这样
在这里插入图片描述
前端会把token保存到sessionStorage中,以后每次请求都带上。
于是,我们的关于保存到session的代码都要做响应的修改
发送验证码的逻辑

 @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);
        // 保存到redis
        stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone, code,LOGIN_CODE_TTL, TimeUnit.MINUTES);

        // 5. TODO 发送验证码-调用第三方平台发送
        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. 从session获取校验验证码
//        Object cacheCode = session.getAttribute("code");
        // 2. 从redis获取校验验证码
        String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);
        String code = loginForm.getCode();
        if(cacheCode == null || !cacheCode.equals(code)){
            // 3. 不一致,直接报错
            return Result.fail("验证码错误!");
        }
        // 4. 根据手机号查询用户 select * from tb_user where phone = ?
        User user = query().eq("phone", phone).one();
        // 5. 判断用户是否存在
        if(user == null){
            // 6. 不存在,创建新用户并保存
            user = createUserWithPhone(phone);
        }

        // 7. 保存用户信息到session
//        session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));
        // 7. 保存用户信息到redis
        // 7.1 生成一个token作为登陆令牌
        String token = UUID.randomUUID().toString(true);
        // 7.2 将User对象转为Hash存储
        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()));
        // 7.3 存储到redis
        String tokenKey = LOGIN_USER_KEY + token;
        stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);
        // 设置token有效期
        stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES);
        // 8. 返回token
        return Result.ok(token);
    }

拦截器


public class LoginInterceptor implements HandlerInterceptor {

    private StringRedisTemplate stringRedisTemplate;

    public LoginInterceptor(StringRedisTemplate stringRedisTemplate){
        this.stringRedisTemplate = stringRedisTemplate;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1. 获取session
//        HttpSession session = request.getSession();
        // 1. 获取请求头中的token
        String token = request.getHeader("authorization");
        if(StrUtil.isBlank(token)){
            // 4. 不存在,拦截
            response.setStatus(401);
            return false;
        }
        // 2. 基于token获取redis中的用户
        String key = RedisConstants.LOGIN_USER_KEY + token;
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);
        // 2. 获取session中的用户
//        UserDTO user = (UserDTO)session.getAttribute("user");
        // 3. 判断用户是否存在
        if(userMap.isEmpty()){
           // 4. 不存在,拦截
            response.setStatus(401);
            return false;
        }

        // 5. 将查询到的hash数据转换成UserDto对象
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
        //6. 存在,保存用户信息到ThreadLocal
        UserHolder.saveUser(userDTO);

        // 7. 刷新token有效期
        stringRedisTemplate.expire(key, RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);
        // 8. 放行
        return true;
    }

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

这里拦截器是我们自定义的,我们不能使用AutoWried让Spring帮助我们导入StringRedisTemplate ,因为这个类都不归Spring管,自然Spring也无法帮我们导入,我们只能使用构造器的方式导入,那在我们的MvcConfig这个配置类中,用到LoginInterceptor的时候,传进去就可以了,于是MvcConfig要修改为这样

package com.hmdp.config;

import com.hmdp.utils.LoginInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import javax.annotation.Resource;

@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

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

解决登录状态刷新的问题

在这里插入图片描述
在加一个拦截器,第一个拦截器负责拦截所有的请求,如果存在就刷新token,并且存入到ThreadLocal中,如果不存在也放行,
第二个拦截器只负责拦截和放行,直接从ThreadLocal中取


/**
 * 第一个拦截器负责拦截所有的请求,如果存在就刷新token,并且存入到ThreadLocal中,如果不存在也放行,
 */
public class RefreshTokenInterceptor implements HandlerInterceptor {

    private StringRedisTemplate stringRedisTemplate;

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

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        // 1. 获取请求头中的token
        String token = request.getHeader("authorization");
        if(StrUtil.isBlank(token)){
            // 为空,也是直接放行,给后面的拦截器判断
            return true;
        }
        // 2. 基于token获取redis中的用户
        String key = RedisConstants.LOGIN_USER_KEY + token;
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);
        // 2. 获取session中的用户
//        UserDTO user = (UserDTO)session.getAttribute("user");
        // 3. 判断用户是否存在
        if(userMap.isEmpty()){
            // 为空,也是直接放行,给后面的拦截器判断
            return true;
        }

        // 5. 将查询到的hash数据转换成UserDto对象
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
        //6. 存在,保存用户信息到ThreadLocal
        UserHolder.saveUser(userDTO);

        // 7. 刷新token有效期
        stringRedisTemplate.expire(key, RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);
        // 8. 放行
        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 {
        // 判断是否需要拦截(ThreadLocal中是否有用户)
        if(UserHolder.getUser() == null){
            // 没有,需要拦截,设置状态码
            response.setStatus(401);
            // 拦截
            return false;
        }
        // 有用户,放行
        return true;
    }

}

于是乎,我们的MvcConfig也要随之修改

package com.hmdp.config;

import com.hmdp.utils.LoginInterceptor;
import com.hmdp.utils.RefreshTokenInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import javax.annotation.Resource;

@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 登录拦截器
        registry.addInterceptor(new LoginInterceptor())
                .excludePathPatterns(
                        "/shop/**",
                        "/voucher/**",
                        "/shop-type/**",
                        "/upload/**",
                        "/blog/hot",
                        "/user/code",
                        "/user/login"
                ).order(1);
        // 刷新拦截器
        registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**").order(0);  // order越小先执行
    }
}

### 如何打包和安装 WPS 加载项 #### 打包方法 为了成功打包 WPS 加载项,开发者需遵循官方文档中的指导[^2]。具体而言,在构建加载项的过程中,需要确保所使用的 `wpsjs` 工具链正确配置。以下是主要步骤: 1. **初始化项目结构** 开发者应先设置好项目的文件夹结构,并按照需求编写前端逻辑代码以及必要的 HTML/CSS/JavaScript 文件。 2. **运行构建命令** 使用 `wpsjs build` 命令对项目进行编译并生成静态资源文件。此过程会将源码转换为适合发布的格式,并将其放置到指定目录下。注意,最终生成的部署路径应当是一个有效的 URL 地址(即以 `http://` 或 `https://` 开头),以便后续能够被正确解析和访问。 3. **验证输出结果** 构建完成后,请确认目标文件夹内包含了所有必需的内容,比如主入口脚本、样式表以及其他依赖资产等。 #### 安装流程 关于如何安装已打包好的 WPS 加载项,则涉及以下几个方面: 1. **上传至服务器** 将通过上述步骤得到的产物上传至支持 HTTPS 协议的远程主机上托管起来。这样做不仅有助于提升安全性还能让更多的用户便捷获取该插件。 2. **利用 WPS 插件管理器导入** 用户可以直接打开 WPS Office 应用程序内的“插件中心”,找到对应选项卡后点击新增按钮;接着粘贴之前准备完毕的有效链接地址完成初步关联动作[^1]。 3. **启用与测试功能模块** 成功添加之后记得重新启动软件实例从而激活新加入的功能组件。此时可以尝试调用一些预设接口看看实际效果是否符合预期设定标准。 ```bash # 示例:执行 wpsjs 构建指令 $ wpsjs build --output-path ./dist/ ``` 以上便是有关于怎样制作以及装配属于自己的个性化定制版 WPS 办公套件附加组件的大致介绍啦!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值