在线教育项目用户登录和注册(四)

本文涉及的产品
云原生内存数据库 Tair,内存型 2GB
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Redis 版,经济版 1GB 1个月
简介: 在线教育项目用户登录和注册(四)

5.2 登录成功:生成token

  • 准备工作已完成

image.png

  • 登录成功,将 EduUser 转换成token,并响应给用户

image.png

 @PostMapping("/login")
    public BaseResult login(@RequestBody EduUser eduUser) {
        //1 校验验证码
        // 1.1 获得redis
        String redisName = "login_verify_code_" + eduUser.getUsername();
        String redisVerifyCode = stringRedisTemplate.opsForValue().get(redisName);
        // 1.2 删除redis
        stringRedisTemplate.delete(redisName);
        // 1.3 校验:无效
        if(redisVerifyCode == null) {
            return BaseResult.error("验证码无效");
        }
        // 1.4 校验:错误
        if(!redisVerifyCode.equalsIgnoreCase(eduUser.getVerifycode())) {
            return BaseResult.error("验证码错误");
        }
        //2 通过service用户登录
        EduUser loginUser = eduUserService.login(eduUser);
        //3 提示
        if(loginUser != null) {
            if("0".equals(loginUser.getStatus())) {
                return BaseResult.error("用户未激活,请先激活");
            }
            if("2".equals(loginUser.getStatus())) {
                return BaseResult.error("临时冻结,36小时");
            }
            if("3".equals(loginUser.getStatus())) {
                return BaseResult.error("该账号已冻结");
            }
            // 需要设置token
            //String token = "admin-token";
            String token = JwtUtils.generateToken(loginUser, jwtProperties.getExpire(), jwtProperties.getPrivateKey());
            return BaseResult.ok("登录成功").append("token", token);
        }
        return BaseResult.error("用户名或密码不匹配");
    }

5.3 查询详情:获得token

5.3.1 基本流程

  • 登录成功后,默认跳转到 / 页面

image.png

访问 / ,在路由中配置跳转的位置

image.png

  • 在跳转 / 页面前,执行vuex中 user/getInfo

16612f255315468ba7dbe250b1bf02b5.png

通过vuex执行ajax请求,查询详情

image.png

5.3.2 查询详情

  • 修改 EduUserController 添加方法

image.png

   @GetMapping("/info")
    public BaseResult info( String token) {
        try {
            //1 通过token 获得用户信息
            EduUser eduUser = JwtUtils.getObjectFromToken(token, jwtProperties.getPublicKey(), EduUser.class);
            //2 模拟数据
/*        Map<String,Object> map = new HashMap<>();
        if("admin-token".equalsIgnoreCase(token)) {
            map.put("roles", Arrays.asList("admin"));        //角色的值必须是数组
            map.put("avatar","https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif");
            map.put("name","张三三");
        } else {
            // 非管理员的权限
        }*/
            //3 真实数据
            Map<String,Object> map = new HashMap<>();
            if(eduUser.getRoles() != null) {
                map.put("roles", eduUser.getRoles().split(","));        //角色的值必须是数组
            } else {
                map.put("roles", Arrays.asList("editor"));        //没有权限的固定:editor
            }
            map.put("avatar","https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif");    //需要完善用户头像
            map.put("name",eduUser.getUsername());
            // 数据返回  baseResult.data --> Map
            return BaseResult.ok("获得权限成功", map);
        } catch (Exception e) {
            return BaseResult.error("获得权限失败");
        }
    }

5.4 过滤器

5.4.1 配置yml

sc:
  jwt:
    secret: sc@Login(Auth}*^31)&czxy% # 登录校验的密钥
    pubKeyPath: D:/rsa/rsa.pub # 公钥地址
    priKeyPath: D:/rsa/rsa.pri # 私钥地址
    expire: 360 # 过期时间,单位分钟
  filter:
    allowPaths:
      - swagger
      - /api-docs
      - /user/login
      - /user/info
      - /user/register
      - /user/sendemail
      - /user/verifycode

5.4.2 配置类

image.png

FilterProperties

package com.czxy.zx.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.List;
/**
 * @author 桐叔
 * @email liangtong@itcast.cn
 */
@Data
@ConfigurationProperties(prefix = "sc.filter")
public class FilterProperties {
    private List<String> allowPaths;
}

JwtProperties

package com.czxy.zx.config;
import com.czxy.zx.utils.RsaUtils;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import java.io.File;
import java.security.PrivateKey;
import java.security.PublicKey;
/**
 * @author 桐叔
 * @email liangtong@itcast.cn
 */
@Configuration
@ConfigurationProperties(prefix = "sc.jwt")
@Data
public class JwtProperties {
    private String secret;
    private String pubKeyPath;
    private String priKeyPath;
    private Integer expire;
    private PublicKey publicKey;
    private PrivateKey privateKey;
    @PostConstruct      //初始化方法注解
    public void init() {
        try {
            File pubFile = new File(pubKeyPath);
            File priFile = new File(priKeyPath);
            if(!pubFile.exists() || ! priFile.exists()) {
                RsaUtils.generateKey(pubKeyPath,priKeyPath,secret);
            }
            // 获得公钥和私钥对象
            this.publicKey = RsaUtils.getPublicKey(pubKeyPath);
            this.privateKey = RsaUtils.getPrivateKey(priKeyPath);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

5.4.3 过滤器

package com.czxy.zx.filter;

import com.czxy.zx.config.FilterProperties;
import com.czxy.zx.config.JwtProperties;
import com.czxy.zx.domain.EduUser;
import com.czxy.zx.utils.JwtUtils;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.List;
/**
 * @author 桐叔
 * @email liangtong@itcast.cn
 */
@Component
@EnableConfigurationProperties(FilterProperties.class)
public class LoginFilter implements GlobalFilter, Ordered {
    @Resource
    private FilterProperties filterProperties;
    @Resource
    private JwtProperties jwtProperties;
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        try {
            //1 获得请求路径
            ServerHttpRequest request = exchange.getRequest();
            String path = request.getURI().getPath();
            System.out.println(path);
            //2 白名单
            List<String> allowPaths = filterProperties.getAllowPaths();
            for (String allowPath : allowPaths) {
                if(path.contains(allowPath)) {
                    // 放行
                    return chain.filter(exchange);
                }
            }
            //3 获得token
            String token = request.getHeaders().getFirst("X-Token");
            //4 校验token
            JwtUtils.getObjectFromToken(token, jwtProperties.getPublicKey(), EduUser.class);
            //5.1  成功,放行
            return chain.filter(exchange);
        } catch (Exception e) {
            e.printStackTrace();
            //5.2 失败,返回提示`token失效`
            ServerHttpResponse response = exchange.getResponse();
            // 响应状态 401 没有权限
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            // 响应数据的编码
            response.getHeaders().add("Content-Type","application/json;charset=UTF-8");
            // 响应“没有权限”提示
            DataBuffer wrap = response.bufferFactory().wrap("没有权限".getBytes(StandardCharsets.UTF_8));
            return exchange.getResponse().writeWith(Flux.just(wrap));
        }
    }
    @Override
    public int getOrder() {
        return 1;
    }
}

5.5 token 无效

image.png

c7bfedffe80140ae998bd69760f2035d.png

 MessageBox.confirm('没有权限,将重新登录', '重新登录提示框', {
      confirmButtonText: '重新登录',
      cancelButtonText: '取消',
      type: 'warning'
    }).then(() => {
      store.dispatch('user/resetToken').then(() => {
        location.reload()
      })
    }) 

6.作业:激活

6.1 分析

需求:

  • 用户点击链接后可以进行账号激活,将用户的状态0改成1
  • 用户重复点击,提示“账号已经激活,无需重复激活”
  • 需要防止其他人帮着激活
  • 激活成功了,跳转到登录页面,“账号已激活,请登录”
  • 1天不激活,激活链接失效,需要重新发送

c2f5727b215442ed84f8ae53add8d10f.png

6.2 完善用户注册

image.png

@PostMapping("/register")
    public BaseResult register(@RequestBody EduUser eduUser) {
        //1 校验
        // 1.1 密码
        if(StringUtils.isBlank(eduUser.getPassword())) {
            ExceptionUtils.cast("密码不能为空");
        }
        if(! eduUser.getPassword().equals(eduUser.getRepassword())) {
            ExceptionUtils.cast("确认密码和密码不一致");
        }
        // 1.2 验证码
        // 1) 获得redis验证码
        String key = "register" + eduUser.getUsername() ;
        String redisVerifyCode = stringRedisTemplate.opsForValue().get(key);
        // 2) 删除redis验证码
        stringRedisTemplate.delete(key);
        // 3) 无效
        if(redisVerifyCode == null) {
            ExceptionUtils.cast("验证码无效");
        }
        // 4) 不对
        if(! redisVerifyCode.equalsIgnoreCase(eduUser.getVerifycode())) {
            ExceptionUtils.cast("验证码错误");
        }
        //2 注册
        boolean register = eduUserService.register(eduUser);
        //3 成功,发送激活邮件
        if(register) {
            // 成功
            // 获得uuid值
            String uuidStr = UUID.randomUUID().toString().replace("-", "");
            // 将uuid存放到redis中,设置有效时间
            stringRedisTemplate.opsForValue().set(uuidStr,eduUser.getUsername(), 24, TimeUnit.HOURS);
            // 发送激活邮件
            UserEmail userEmail = new UserEmail();
            userEmail.setEmail(eduUser.getEmail());
            userEmail.setTitle("XXX平台激活邮件");
            // TODO 作业:用户激活
            //String url = "http://localhost:10010/user-service/user/active?username=" + eduUser.getUsername();
            String url = "http://localhost:9527/#/active?uuid=" + uuidStr;
            String msg = eduUser.getUsername() + ",你好:<br/>" +
                        "<a href='"+url+"'>点击</a>链接进行账号激活。<br/>" +
                        "如果不能点击,请复制下面的连接:" + url;
            userEmail.setText(msg);
            System.out.println(eduUser.getId());
            //3 发送邮件-将邮件信息存放mq
            String jsonStr = JSON.toJSONString(userEmail);
            rabbitTemplate.convertAndSend("", RabbitEmailConfig.QUEUE_NAME, jsonStr);
            return BaseResult.ok("注册成功,请进行账号激活");
        }
        return BaseResult.error("注册失败");
    }

6.3 用户激活

6.3.1 前端实现

  • 步骤:
  • 步骤1:编写激活页面
  • 步骤2:编写ajax函数
  • 步骤3:添加白名单
  • 步骤1:编写激活页面

image.png


         
  • 步骤2:编写ajax函数

image.png


         

步骤3:添加白名单

image.png

6.3.2 后端实现

image.png


         

6.4 完善用户登录

image.png


         
相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
8月前
|
存储 小程序 前端开发
微信小程序进阶——后台交互个人中心授权登录
微信小程序进阶——后台交互个人中心授权登录
132 0
|
7月前
|
小程序 前端开发 安全
微信小程序OA会议系统个人中心授权登入
微信小程序OA会议系统个人中心授权登入
45 0
|
6月前
|
小程序 前端开发 安全
微信小程序——后台交互个人中心授权登录
微信小程序——后台交互个人中心授权登录
160 0
|
JavaScript Java 数据库
企业微信接入系列-扫码绑定/登录
讲述在企业后台管理平台账号绑定企业微信以及企业微信扫码登录企业管理平台
企业微信接入系列-扫码绑定/登录
|
6月前
|
前端开发
HTML+CSS实现小米账号注册界面
HTML+CSS实现小米账号注册界面
|
7月前
|
NoSQL 前端开发 数据库
淘东电商项目(36) -SSO单点登录(退出功能)
淘东电商项目(36) -SSO单点登录(退出功能)
35 0
|
7月前
|
JSON 前端开发 NoSQL
淘东电商项目(27) -门户登出功能
淘东电商项目(27) -门户登出功能
30 0
|
7月前
|
前端开发 NoSQL 数据库
淘东电商项目(26) -门户登录功能
淘东电商项目(26) -门户登录功能
25 0
|
7月前
|
前端开发
淘东电商项目(33) -SSO单点登录(改造SSO认证服务登录界面)
淘东电商项目(33) -SSO单点登录(改造SSO认证服务登录界面)
44 0
|
9月前
|
XML 缓存 NoSQL
手把手实现第三方社交登录方式微信登录
手把手实现第三方社交登录方式微信登录
118 0