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

🔎这里是在线教育,关注我学习Java不迷路
👍如果对你有帮助,给博主一个免费的点赞以示鼓励
欢迎各位🔎点赞👍评论收藏⭐️

👀专栏介绍

项目目前主要更新在线教育,一起学习一起进步。

👀本期介绍

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

资料获取

链接:https://pan.baidu.com/s/12tchZo7zwmvMqWBNY9BLog 
提取码:8888 

文章目录

1.1 数据库

1.2 创建JavaBean

1.3 用户服务环境

1.4 用户服务基本模块

2. MQ服务

3. 用户登录

3.0 取消登录表单校验

3.1 发送验证码邮件

3.1.1 前端:显示表单

3.1.2 前端:发送邮件

3.1.3 完善 EduUser

3.1.4 用于封装与MQ交互的数据

3.1.5 编写Rabbit配置类

3.1.6 后端实现

3.2 编写MQ:发送邮件 

3.2.1 拷贝配置类

3.2.2 监听器发送邮件

3.3 后端:用户登录

3.3.0 前端登录流程

3.3.1 修改前端api

3.3.2 修改前端请求数据

3.3.2 后端实现

3.3.4 前端修改

3.3.5 数据要求 admin-token

3.4 登录成功后查询权限

3.4.1 分析

3.4.2 前端完善

3.4.3 后端实现

4. 用户注册

4.0 整体流程

4.1 图片验证码

4.2 后端实现

4.2.1 完善EduUser

4.2.2 service实现

4.3.3 controller

4.3 前端实现

4.3.1 显示页面

4.3.2 前端 api

4.3.3 注册页面 

4.3.4 注册功能

5 整合JWT

5.0 分析

5.1 搭建环境

5.1.1 拷贝坐标

5.1.2 复制yml配置

5.1.3 拷贝配合类

5.1.4 拷贝工具类

5.1.5 生成秘钥(可选)

5.2 登录成功:生成token

5.3 查询详情:获得token

5.3.1 基本流程

5.3.2 查询详情

5.4 过滤器

5.4.1 配置yml

5.4.2 配置类 

5.4.3 过滤器

5.5 token 无效

6.作业:激活

6.1 分析

6.2 完善用户注册

6.3 用户激活

6.3.1 前端实现

6.3.2 后端实现

6.4 完善用户登录

后端用户服务

1.1 数据库

CREATE DATABASE zx_edu_user;
USE zx_edu_user;
​
CREATE TABLE `edu_user` (
  `id` INT(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
  `username` VARCHAR(50) DEFAULT NULL COMMENT '用户名',
  `password` VARCHAR(255) DEFAULT NULL COMMENT '用户密码',
  `phone` CHAR(11) DEFAULT NULL COMMENT '电话',
  `email` VARCHAR(50) DEFAULT NULL COMMENT '邮箱',
  `roles` VARCHAR(30) DEFAULT NULL COMMENT '角色,多个值使用逗号分隔,例如:admin,editor',
  `created` DATE DEFAULT NULL
) ;
​
​
INSERT INTO `edu_user` VALUES (1, 'jack', '1234', '13699282444', 'itcast_lt@126.com', 'admin', '2015-10-20');
INSERT INTO `edu_user` VALUES (2, 'rose', '1234', '13377776666', 'itcast_lt@126.com', 'editor', NULL);
INSERT INTO `edu_user` VALUES (3, 'tom', '1234', '15533336666', 'itcast_lt@126.com', 'admin,editor', '2020-02-14');

1.2 创建JavaBean

package com.czxy.zx.domain;
​
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import lombok.Data;
​
import java.io.Serializable;
import java.util.Date;
​
/**
 *
 */
@Data
@TableName("edu_user")
@ApiModel(value = "EduUser对象",description = "用户")
public class EduUser {
​
    @TableId(type = IdType.AUTO)
    private Integer id;
    //用户名
    private String username;
    //用户密码
    private String password;
    //电话
    private String phone;
    //邮箱
    private String email;
    //角色,多个值使用逗号分隔,例如:admin,editor
    private String roles;
    //创建时间
    private Date created;
​
}

1.3 用户服务环境

  • 创建项目:zx-service-user

  • 修改pom文件,添加坐标

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>zx-parent31</artifactId>
            <groupId>com.czxy.zx</groupId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    ​
        <artifactId>zx-service-user31</artifactId>
    ​
        <dependencies>
            <!--web起步依赖-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <!-- nacos 客户端 -->
            <dependency>
                <groupId>com.alibaba.nacos</groupId>
                <artifactId>nacos-client</artifactId>
            </dependency>
    ​
            <!-- nacos 服务发现 -->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            </dependency>
    ​
            <!--swagger2-->
            <dependency>
                <groupId>io.springfox</groupId>
                <artifactId>springfox-swagger2</artifactId>
            </dependency>
            <dependency>
                <groupId>io.springfox</groupId>
                <artifactId>springfox-swagger-ui</artifactId>
            </dependency>
    ​
            <!-- feign 远程调用 -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-openfeign</artifactId>
            </dependency>
    ​
            <!--测试-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
            </dependency>
    ​
            <!-- mybatis plus-->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>${mybatis.plus.version}</version>
            </dependency>
            <!-- mysql驱动 -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
            </dependency>
            <!--自定义项目-->
            <dependency>
                <groupId>com.czxy.zx</groupId>
                <artifactId>zx-common31</artifactId>
            </dependency>
            <dependency>
                <groupId>com.czxy.zx</groupId>
                <artifactId>zx-domain31</artifactId>
            </dependency>
    ​
            <!-- redis 启动器 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-redis</artifactId>
            </dependency>
            <!-- JavaMail 启动器 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-mail</artifactId>
            </dependency>
            <!-- MQ 启动器 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-amqp</artifactId>
            </dependency>
    ​
            <!-- fastjson -->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
            </dependency>
    ​
            <!--开发者工具-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-devtools</artifactId>
                <optional>true</optional>
            </dependency>
    ​
        </dependencies>
    ​
    ​
    </project>
  • 创建yml文件

    # 服务端口号
    server:
      port: 9010
    # 服务名
    spring:
      application:
        name: user-service
      datasource:
        driverClassName: com.mysql.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/zx_edu_user?useUnicode=true&characterEncoding=utf8
        username: root
        password: 1234
        druid:    #druid 连接池配置
          initial-size: 1       #初始化连接池大小
          min-idle: 1           #最小连接数
          max-active: 20        #最大连接数
          test-on-borrow: true  #获取连接时候验证,会影响性能
      cloud:
        nacos:
          discovery:
            server-addr: 127.0.0.1:8848   #nacos服务地址
      redis:
        database: 0       #数据库索引,取值0-15,表示16个库可选择
        host: 127.0.0.1   #服务器地址
        port: 6379        #服务器连接端口号
      mail:
        host: smtp.126.com          #发送邮件服务器
        username: itcast_lt@126.com #账号
        password: 1qaz2wsx          #密码
        default-encoding: UTF-8     #默认编码时
      rabbitmq:
        host: 127.0.0.1
        port: 5672
        username: guest
        passowrd: guest
        virtualHost: /
      devtools:
        restart:
          enabled: true  #设置开启热部署
          additional-paths: src/main/java #重启目录
          exclude: WEB-INF/**
      freemarker:
        cache: false    #页面不加载缓存,修改即时生效
    ​
    #开启log4j打印SQL语句
    logging:
      level:
        com:
          czxy:
            zx:
              user:
                mapper: debug
    ​
    # mp日志打印
    mybatis-plus:
      configuration:
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
      global-config:
        db-config:
          logic-delete-value: 1
          logic-not-delete-value: 0
    ​
  • 启动类 

package com.czxy.cz;
​
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
​
/**
 * @author 桐叔
 * @email liangtong@itcast.cn
 */
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class UserServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserServiceApplication.class,args);
    }
}
​

1.4 用户服务基本模块

  • 拷贝配置类

  • 编写mapper

    package com.czxy.zx.user.mapper;
    ​
    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import com.czxy.zx.domain.EduUser;
    import org.apache.ibatis.annotations.Mapper;
    ​
    /**
     * @author 桐叔
     * @email liangtong@itcast.cn
     */
    @Mapper
    public interface EduUserMapper extends BaseMapper<EduUser> {
    }
    ​
    
  • 编写service接口

    package com.czxy.zx.user.service;
    ​
    import com.baomidou.mybatisplus.extension.service.IService;
    import com.czxy.zx.domain.EduUser;
    ​
    /**
     * @author 桐叔
     * @email liangtong@itcast.cn
     */
    public interface EduUserService extends IService<EduUser> {
    }
    ​
    
  • 编写service实现类

    package com.czxy.zx.user.service.impl;
    ​
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.czxy.zx.domain.EduUser;
    import com.czxy.zx.user.mapper.EduUserMapper;
    import com.czxy.zx.user.service.EduUserService;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    ​
    /**
     * @author 桐叔
     * @email liangtong@itcast.cn
     */
    @Service
    @Transactional
    public class EduUserServiceImpl extends ServiceImpl<EduUserMapper, EduUser> implements EduUserService {
    }
    ​
    
  • 编写controller

    package com.czxy.zx.user.controller;
    
    import com.czxy.zx.user.service.EduUserService;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.annotation.Resource;
    
    /**
     * @author 桐叔
     * @email liangtong@itcast.cn
     */
    @RestController
    @RequestMapping("/user")
    public class EduUserController {
    
        @Resource
        private EduUserService eduUserService;
    
    
    }
    
    

2. MQ服务

  • 创建项目: zx-mq31

  • 编写pom文件

        <dependencies>
            <!-- MQ 启动器 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-amqp</artifactId>
            </dependency>
            <!-- JavaMail 启动器 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-mail</artifactId>
            </dependency>
            <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
            </dependency>
            <!--自定义项目-->
            <dependency>
                <groupId>com.czxy.zx</groupId>
                <artifactId>zx-common31</artifactId>
            </dependency>
        </dependencies>
    
  • 编写yml文件

    # 服务端口号
    server:
      port: 8900
    # 服务名
    spring:
      application:
        name: mq-service
      mail:
        host: smtp.126.com          #发送邮件服务器
        username: itcast_lt@126.com #账号
        password: 1qaz2wsx          #密码
        default-encoding: UTF-8     #默认编码时
      rabbitmq:
        host: 127.0.0.1
        port: 5672
        username: guest
        passowrd: guest
        virtualHost: /
    
  • 拷贝工具类

  • 编写启动类

    package com.czxy.zx;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    /**
     * @author 桐叔
     * @email liangtong@itcast.cn
     */
    @SpringBootApplication
    public class MQApplication {
        public static void main(String[] args) {
            SpringApplication.run(MQApplication.class,args);
        }
    }

3. 用户登录

  • element ui admin 执行流程图

3.0 取消登录表单校验

3.1 发送验证码邮件

3.1.1 前端:显示表单

  • 登录页面:@/views/login/index.vue

<!-- 邮箱 -->
      <el-form-item prop="email">
        <span class="svg-container">
          <svg-icon icon-class="email" />
        </span>
        <el-input
          ref="email"
          v-model="loginForm.email"
          placeholder="请输入邮箱"
          name="email"
          type="text"
          tabindex="3"
          style="width:70%;"
        />
        <el-button  type="primary" style="width:20%" @click.native.prevent="sendEmail">发送</el-button>
      </el-form-item>
      <!-- 验证码 -->
      <el-form-item prop="verifycode">
        <span class="svg-container">
          <svg-icon icon-class="guide" />
        </span>
        <el-input
          ref="verifycode"
          v-model="loginForm.verifycode"
          placeholder="请输入验证码"
          name="verifycode"
          type="text"
          tabindex="4"
        />
      </el-form-item>

3.1.2 前端:发送邮件

  • 编写API,发送邮件

export function send(user) {
  return axios.post('/user-service/user/sendemail',user);
}
  • 调用

    async sendEmail() {
      let { message } = await send( this.loginForm)
      this.$message.success( message )
    }

3.1.3 完善 EduUser

  • 用于封装表单提交的用户数据

package com.czxy.zx.domain;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import lombok.Data;

import java.util.Date;

/**
 *
 */
@Data
//@TableName("edu_user")
@ApiModel(value = "EduUser对象",description = "用户")
public class EduUser {

    @TableId(value="id" , type = IdType.AUTO)
    private Integer id;
    //用户名
    private String username;
    //用户密码
    private String password;
    //电话
    private String phone;
    //邮箱
    private String email;
    //角色,多个值使用逗号分隔,例如:admin,editor
    private String roles;
    //创建时间
    private Date created;
    //状态:0 未激活、1已激活
    private String status;

    @TableField(exist = false)
    private String verifycode;

}

3.1.4 用于封装与MQ交互的数据

package com.czxy.zx.vo; import lombok.Data; /** * @author 桐叔 * @email liangtong@itcast.cn */ @Data public class UserEmail { private String username; private String email; private String text; }

3.1.5 编写Rabbit配置类

package com.czxy.zx.user.config;

import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

/**
 * @author 桐叔
 * @email liangtong@itcast.cn
 */
@Component
public class RabbitEmailConfig {
    // 队列的名称
    public static final String QUEUE_NAME = "zx-email";

    @Bean
    public Queue queue() {
        return new Queue(QUEUE_NAME);
    }
}

3.1.6 后端实现

/**
 * @author 桐叔
 * @email liangtong@itcast.cn
 */
@RestController
@RequestMapping("/user")
public class EduUserController {

    @Resource
    private EduUserService eduUserService;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private RabbitTemplate rabbitTemplate;

    @PostMapping("/sendemail")
    public BaseResult sendemail(@RequestBody EduUser eduUser) {
        //1 随机字符串
        Random random = new Random();
        // [0,8999) --> [1000,9999)
        int randomNumber = random.nextInt(8999) + 1000;

        //2 发送redis一份
        stringRedisTemplate.opsForValue().set("login" + eduUser.getUsername(), randomNumber + "");

        //3 mq存放
        UserEmail userEmail = new UserEmail();
        userEmail.setUsername(eduUser.getUsername());
        userEmail.setEmail(eduUser.getEmail());
        userEmail.setText("登录验证码是:" + randomNumber);

        String userEmailStr = JSONObject.toJSONString(userEmail);
        rabbitTemplate.convertAndSend(RabbitEmailConfig.QUEUE_NAME , userEmailStr);

        return BaseResult.ok("发送中,请查收");
    }

3.2 编写MQ:发送邮件 

3.2.1 拷贝配置类

  • 与登录模块使用的配置类相同 

package com.czxy.zx.config;

import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

/**
 * @author 桐叔
 * @email liangtong@itcast.cn
 */
@Component
public class RabbitEmailConfig {
    // 队列的名称
    public static final String QUEUE_NAME = "zx-email";

    @Bean
    public Queue queue() {
        return new Queue(QUEUE_NAME);
    }
}

3.2.2 监听器发送邮件

package com.czxy.zx.listener;

import com.alibaba.fastjson.JSONObject;
import com.czxy.zx.config.RabbitEmailConfig;
import com.czxy.zx.utils.EmailUtils;
import com.czxy.zx.vo.UserEmail;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * @author 桐叔
 * @email liangtong@itcast.cn
 */
@Component
public class RabbitEmailListener {
    @Resource
    private JavaMailSender javaMailSender;

    @RabbitListener(queues = RabbitEmailConfig.QUEUE_NAME)
    public void sendEmail(String message) {
        //1 将消息转换成 UserEmail
        UserEmail userEmail = JSONObject.parseObject(message, UserEmail.class);

        //2 发送邮件,如果邮箱为空,将出现循环异常
       if(userEmail.getEmail() != null) { EmailUtils.sendEmail(javaMailSender,userEmail.getEmail(),userEmail.getText());
       }

    }

}

3.3 后端:用户登录

3.3.0 前端登录流程

  • 步骤一:登录页面 @/views/login/index.vue

  • 步骤二:登录调用 vuex

  • 步骤三:查看vuex

  • 步骤四:调用ajax

  • 步骤五:确定ajax发送位置

3.3.1 修改前端api

export function login(user) {
  // 真实数据
  return axios.post('/user-service/user/login',user);
  // 临时模拟
  // return axios.post('/teacher-service/user/login',user);
}

3.3.2 修改前端请求数据

3.3.2 后端实现

  • EduUserController 添加 login 方法

package com.czxy.zx.user.controller;

import com.alibaba.fastjson.JSON;
import com.czxy.zx.domain.EduUser;
import com.czxy.zx.user.config.RabbitEmailConfig;
import com.czxy.zx.user.service.EduUserService;
import com.czxy.zx.vo.BaseResult;
import com.czxy.zx.vo.UserEmail;
import org.apache.commons.lang3.RandomUtils;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

/**
 * @author 桐叔
 * @email liangtong@itcast.cn
 */
@RestController
@RequestMapping("/user")
public class EduUserController {

    @Resource
    private EduUserService eduUserService;

    @Resource
    private RabbitTemplate rabbitTemplate;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @PostMapping("/send")
    public BaseResult send(@RequestBody EduUser eduUser) {
        //1 随机生成验证码
        int num = RandomUtils.nextInt(1000, 10000);
        //2 封装发送邮件的数据--
        UserEmail userEmail = new UserEmail();
        userEmail.setEmail(eduUser.getEmail());
        userEmail.setTitle("用户登录验证码");
        userEmail.setText(eduUser.getUsername() + "您好,本次验证码:" + num);

        //3 发送邮件-将邮件信息存放mq
        String jsonStr = JSON.toJSONString(userEmail);
        rabbitTemplate.convertAndSend("", RabbitEmailConfig.QUEUE_NAME, jsonStr);

        //4 将验证码存放redis
        String redisName = "login_verify_code_" + eduUser.getUsername();
        stringRedisTemplate.opsForValue().set(redisName, num + "");

        //5 提示
        return BaseResult.ok("验证码发送成功!");

    }

    @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) {
            // 需要设置token
            String token = "admin-token";
            return BaseResult.ok("登录成功").append("token", token);
        }
        return BaseResult.error("用户名或密码不匹配");
    }

}

3.3.4 前端修改

3.3.5 数据要求 admin-token

登录成功后,查询用户详情的使用

3.4 登录成功后查询权限

3.4.1 分析

  • 登录时,返回一个固定的字符串:admin-token 或 editor-token

  • 登录成功后,查询用户详情将携带固定字符串

  • 根据固定字符串查询用户的权限,并返回固定的信息:

    {
    	roles: ['admin'],				//用户角色 或 [editor] 或 [admin,editor]
    	avatar: '',						//头像图片地址
    	name: '',						//用户名
    }

  • 查询详情ajax调用时机

3.4.2 前端完善

3.4.3 后端实现

  • 完善 EduUserController,添加查询详情功能

    /**
         * 查询详情
         * @param token
         * @return
         */
        @GetMapping("/info")
        public BaseResult info(String token) {
            System.out.println(token);
            Map<String,Object> map = new HashMap<>();
            // 根据固定字符串拼凑数据
            if("admin-token".equals(token)) {
                map.put("roles", Arrays.asList("admin"));
                map.put("avatar","https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif");
                map.put("name","Super Admin");
            } else {
                map.put("roles", Arrays.asList("editor"));
                map.put("avatar","https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif");
                map.put("name","Normal Editor");
            }
    
            return BaseResult.ok("成功", map);
        }

4. 用户注册

4.0 整体流程

  • 示意图

4.1 图片验证码

package com.czxy.zx.user.controller;

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.annotation.Resource;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * Created by liangtong.
 */
@Controller
@RequestMapping("/verifycode")
public class VerifyCodeController {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    //   /verifycode/jack
    @GetMapping("/{username}")
    public void verifyCode(@PathVariable("username") String username , HttpServletResponse response ) throws IOException {

        //字体只显示大写,去掉了1,0,i,o几个容易混淆的字符
        String VERIFY_CODES = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";

        int IMG_WIDTH = 72;
        int IMG_HEIGTH = 27;
        Random random = new Random();
        //创建图片
        BufferedImage image = new BufferedImage(IMG_WIDTH, IMG_HEIGTH, BufferedImage.TYPE_INT_RGB);
        //画板
        Graphics g = image.getGraphics();
        //填充背景
        g.setColor(Color.WHITE);
        g.fillRect(1,1,IMG_WIDTH-2,IMG_HEIGTH-2);

        g.setFont(new Font("楷体",Font.BOLD,25));

        StringBuilder sb = new StringBuilder();
        //写字
        for(int i = 1 ; i <= 4 ; i ++){
            //随机颜色
            g.setColor(new Color(random.nextInt(255),random.nextInt(255),random.nextInt(255)));
            int len = random.nextInt(VERIFY_CODES.length());
            String str = VERIFY_CODES.substring(len,len+1);
            sb.append(str);
            g.drawString(str, IMG_WIDTH / 6 * i , 22 );
        }

        System.out.println("验证码:" + sb.toString());
        // 将验证码保存redis中
        stringRedisTemplate.opsForValue().set("register" + username , sb.toString() );


        // 生成随机干扰线
        for (int i = 0; i < 30; i++) {
            //随机颜色
            g.setColor(new Color(random.nextInt(255),random.nextInt(255),random.nextInt(255)));
            int x = random.nextInt(IMG_WIDTH - 1);
            int y = random.nextInt(IMG_HEIGTH - 1);
            int x1 = random.nextInt(12) + 1;
            int y1 = random.nextInt(6) + 1;
            g.drawLine(x, y, x - x1, y - y1);
        }

        //响应到浏览器
        ImageIO.write(image,"jpeg", response.getOutputStream());

    }
}

4.2 后端实现

4.2.1 完善EduUser

package com.czxy.zx.domain;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import lombok.Data;

import java.util.Date;

/**
 *
 */
@Data
//@TableName("edu_user")
@ApiModel(value = "EduUser对象",description = "用户")
public class EduUser {

    @TableId(value="id" , type = IdType.AUTO)
    private Integer id;
    //用户名
    private String username;
    //用户密码
    private String password;
    //电话
    private String phone;
    //邮箱
    private String email;
    //角色,多个值使用逗号分隔,例如:admin,editor
    private String roles;
    //创建时间
    private Date created;
    //状态:0 未激活、1已激活
    private String status;

    @TableField(exist = false)
    private String verifycode;

    @TableField(exist = false)
    private String repassword;

}

4.2.2 service实现

  • service 接口

    package com.czxy.zx.user.service;
    
    import com.baomidou.mybatisplus.extension.service.IService;
    import com.czxy.zx.domain.EduUser;
    
    /**
     * @author 桐叔
     * @email liangtong@itcast.cn
     */
    public interface EduUserService extends IService<EduUser> {
        boolean register(EduUser eduUser);
    }
    
    
  • service实现类

    package com.czxy.zx.user.service.impl;
    
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.czxy.zx.domain.EduUser;
    import com.czxy.zx.user.mapper.EduUserMapper;
    import com.czxy.zx.user.service.EduUserService;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    import java.util.Date;
    
    /**
     * @author 桐叔
     * @email liangtong@itcast.cn
     */
    @Service
    @Transactional
    public class EduUserServiceImpl extends ServiceImpl<EduUserMapper, EduUser> implements EduUserService {
        @Override
        public boolean register(EduUser eduUser) {
            //1 校验,用户名存在不允许注册
            QueryWrapper<EduUser> queryWrapper = new QueryWrapper();
            queryWrapper.eq("username", eduUser.getUsername());
            EduUser findUser = this.baseMapper.selectOne(queryWrapper);
            if(findUser != null) {
                throw new EduException("用户名已存在");
            }
    
            //2 自动生成数据
            eduUser.setCreated(new Date());    //创建时间
            eduUser.setStatus("0");            //登录状态
    
            //3 保存
            int insert = this.baseMapper.insert(eduUser);
    
            //4 提示
            return insert == 1;
        }
    }
    
    

4.3.3 controller

/**
     * 注册功能
     * @param eduUser
     * @return
     */
    @PostMapping("/register")
    public BaseResult register(@RequestBody EduUser eduUser) {

        //1.1 校验验证码
        String redisVerifyCode = stringRedisTemplate.opsForValue().get("login" + eduUser.getUsername());
        stringRedisTemplate.delete("login" + eduUser.getUsername());
        if(redisVerifyCode == null) {
            return BaseResult.error("验证码无效");
        }
        if(! redisVerifyCode.equalsIgnoreCase(eduUser.getVerifycode())) {
            return BaseResult.error("验证码错误");
        }
        //1.2 密码校验
        if(eduUser.getPassword() == null) {
            return BaseResult.error("密码不能为空");
        }
        if(! eduUser.getPassword().equals(eduUser.getRepassword())) {
            return BaseResult.error("密码和确认密码不一致");
        }

        //2 注册
        boolean result = eduUserService.register(eduUser);

        //3 处理结果
        if(result) {
            // 成功
            // 3.1 生成UUID
            String uuid = UUID.randomUUID().toString().replace("-","");
            // 3.2 生成激活路由
            String url = "http://localhost:8080/active/"+eduUser.getUsername()+"/" + uuid;    //访问前端
            // 3.3 发送激活邮件
            String text = eduUser.getUsername() + "您好:<br/>您使用本网站的激活程序,请<a href='"+url+"'>点击激活</a>";

            // 3.4 发送邮件
            UserEmail userEmail = new UserEmail();
            userEmail.setUsername(eduUser.getUsername());
            userEmail.setEmail(eduUser.getEmail());
            userEmail.setText(text);
            String userEmailStr = JSONObject.toJSONString(userEmail);
            rabbitTemplate.convertAndSend(RabbitEmailConfig.QUEUE_NAME , userEmailStr);
            // 3.5 保存激活状态码
            stringRedisTemplate.opsForValue().set("active" + eduUser.getUsername() , uuid , 5 , TimeUnit.MINUTES);

            return BaseResult.ok("注册成功");
        }

        return BaseResult.ok("注册失败");
    }

4.3 前端实现

4.3.1 显示页面

  • 创建页面 @/views/edu/user/register.vue

<template>
  <div>
    注册
  </div>
</template>

<script>
export default {

}
</script>

<style>

</style>

  • 编写路由

  {
    path: '/register',
    component: () => import('@/views/edu/user/register'),
    hidden: true			//登录成功后,左侧菜单中不显示
  }
  • 修改登录页面

<el-button class="thirdparty-button" type="primary" style="right:80px;" @click="showDialog=true">
   三方登录
</el-button>
<el-button class="thirdparty-button" type="primary" @click="$router.push('/register')">
    注册
 </el-button>
  • 将注册连接添加到白名单

4.3.2 前端 api

export function register(user) {
  // 真实数据
  return axios.post('/user-service/user/register',user);
}

4.3.3 注册页面 

<template>
  <div class="login-container">
    <el-form ref="loginForm" :model="loginForm" class="login-form" >

      <div class="title-container">
        <h3 class="title">注册表单</h3>
      </div>

      <el-form-item prop="username">
        <span class="svg-container">
          <svg-icon icon-class="user" />
        </span>
        <el-input
          ref="username"
          v-model="loginForm.username"
          placeholder="用户名"
          type="text"
          tabindex="1"
          @blur="reload"
        />
      </el-form-item>

      <el-tooltip v-model="capsTooltip" content="Caps lock is On" placement="right" manual>
        <el-form-item prop="password">
          <span class="svg-container">
            <svg-icon icon-class="password" />
          </span>
          <el-input
            :key="passwordType"
            ref="password"
            v-model="loginForm.password"
            :type="passwordType"
            placeholder="密码"
            tabindex="2"
            autocomplete="on"
            @keyup.native="checkCapslock"
            @blur="capsTooltip = false"
            @keyup.enter.native="handleLogin"
          />
          <span class="show-pwd" @click="showPwd">
            <svg-icon :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'" />
          </span>
        </el-form-item>
      </el-tooltip>
      <el-tooltip v-model="recapsTooltip" content="Caps lock is On" placement="right" manual>
        <el-form-item prop="repassword">
          <span class="svg-container">
            <svg-icon icon-class="password" />
          </span>
          <el-input
            :key="repasswordType"
            ref="repassword"
            v-model="loginForm.repassword"
            :type="repasswordType"
            placeholder="确认密码"
            tabindex="3"
            autocomplete="on"
            @keyup.native="recheckCapslock"
            @blur="recapsTooltip = false"
          />
          <span class="show-pwd" @click="reshowPwd">
            <svg-icon :icon-class="repasswordType === 'password' ? 'eye' : 'eye-open'" />
          </span>
        </el-form-item>
      </el-tooltip>

      <!-- 手机 -->
      <el-form-item prop="phone">
        <span class="svg-container">
          <svg-icon icon-class="wechat" />
        </span>
        <el-input
          ref="phone"
          v-model="loginForm.phone"
          placeholder="请输入手机号"
          name="phone"
          type="text"
          tabindex="4"
        />
      </el-form-item>

      <!-- 邮箱 -->
      <el-form-item prop="email">
        <span class="svg-container">
          <svg-icon icon-class="email" />
        </span>
        <el-input
          ref="email"
          v-model="loginForm.email"
          placeholder="请输入邮箱"
          name="email"
          type="text"
          tabindex="5"
        />
      </el-form-item>
      <!-- 验证码 -->
      <el-form-item prop="verifycode">
        <span class="svg-container">
          <svg-icon icon-class="guide" />
        </span>
        <el-input
          ref="verifycode"
          v-model="loginForm.verifycode"
          placeholder="请输入验证码"
          name="verifycode"
          type="text"
          tabindex="6"
          style="width:70%;"
        />
        <img :src="rerifycodeImg" @click="reload" alt="">
      </el-form-item>

      <el-button type="primary" style="width:100%;margin-bottom:30px;" @click.native.prevent="userRegister">注册</el-button>

    </el-form>
  </div>
</template>

<script>
export default {
  data() {
    return {
      loginForm: {

      },
      capsTooltip: false,
      passwordType: 'password',
      recapsTooltip: false,
      repasswordType: 'password',
      rerifycodeImg: '',
    }
  },
  methods: {
    showPwd() {
      if (this.passwordType === 'password') {
        this.passwordType = ''
      } else {
        this.passwordType = 'password'
      }
      this.$nextTick(() => {
        this.$refs.password.focus()
      })
    },
    checkCapslock(e) {
      const { key } = e
      this.capsTooltip = key && key.length === 1 && (key >= 'A' && key <= 'Z')
    },
    reshowPwd() {
      if (this.repasswordType === 'password') {
        this.repasswordType = ''
      } else {
        this.repasswordType = 'password'
      }
      this.$nextTick(() => {
        this.$refs.repassword.focus()
      })
    },
    recheckCapslock(e) {
      const { key } = e
      this.recapsTooltip = key && key.length === 1 && (key >= 'A' && key <= 'Z')
    },
    reload() {
      //  "路径?t=" + new Date() ,提供一个t变量,用于唯一标识每一次访问路径
      this.rerifycodeImg = `http://localhost:10010/v2/user-service/verifycode/${this.loginForm.username}?t=` + new Date().getTime()
    },
    userRegister() {

    }
  },
}
</script>

<style lang="scss">
/* 修复input 背景不协调 和光标变色 */
/* Detail see https://github.com/PanJiaChen/vue-element-admin/pull/927 */

$bg:#283443;
$light_gray:#fff;
$cursor: #fff;

@supports (-webkit-mask: none) and (not (cater-color: $cursor)) {
  .login-container .el-input input {
    color: $cursor;
  }
}

/* reset element-ui css */
.login-container {
  .el-input {
    display: inline-block;
    height: 47px;
    width: 85%;

    input {
      background: transparent;
      border: 0px;
      -webkit-appearance: none;
      border-radius: 0px;
      padding: 12px 5px 12px 15px;
      color: $light_gray;
      height: 47px;
      caret-color: $cursor;

      &:-webkit-autofill {
        box-shadow: 0 0 0px 1000px $bg inset !important;
        -webkit-text-fill-color: $cursor !important;
      }
    }
  }

  .el-form-item {
    border: 1px solid rgba(255, 255, 255, 0.1);
    background: rgba(0, 0, 0, 0.1);
    border-radius: 5px;
    color: #454545;
  }
}
</style>

<style lang="scss" scoped>
$bg:#2d3a4b;
$dark_gray:#889aa4;
$light_gray:#eee;

.login-container {
  min-height: 100%;
  width: 100%;
  background-color: $bg;
  overflow: hidden;

  .login-form {
    position: relative;
    width: 520px;
    max-width: 100%;
    padding: 160px 35px 0;
    margin: 0 auto;
    overflow: hidden;
  }

  .tips {
    font-size: 14px;
    color: #fff;
    margin-bottom: 10px;

    span {
      &:first-of-type {
        margin-right: 16px;
      }
    }
  }

  .svg-container {
    padding: 6px 5px 6px 15px;
    color: $dark_gray;
    vertical-align: middle;
    width: 30px;
    display: inline-block;
  }

  .title-container {
    position: relative;

    .title {
      font-size: 26px;
      color: $light_gray;
      margin: 0px auto 40px auto;
      text-align: center;
      font-weight: bold;
    }
  }

  .show-pwd {
    position: absolute;
    right: 10px;
    top: 7px;
    font-size: 16px;
    color: $dark_gray;
    cursor: pointer;
    user-select: none;
  }

  .thirdparty-button {
    position: absolute;
    right: 0;
    bottom: 6px;
  }

  @media only screen and (max-width: 470px) {
    .thirdparty-button {
      display: none;
    }
  }
}
</style>
  • 获得 .env.development 文件中配置内容

    reload() {
      //this.rerifycodeImg = `http://localhost:10010/v2/user-service/verifycode/${this.loginForm.username}?t=${new Date().getTime()}`
      this.rerifycodeImg = `${process.env.VUE_APP_BASE_API}/user-service/verifycode/${this.loginForm.username}?t=${new Date().getTime()}`
    }

4.3.4 注册功能

    async userRegister() {
      let {message} = await register(this.loginForm)
      this.$message.success(message)
      //跳转到登录
      this.$router.push('/login')
    }

5 整合JWT

5.0 分析

5.1 搭建环境

5.1.1 拷贝坐标

        <!--JavaBean工具类,用于JavaBean数据封装-->
        <dependency>
            <groupId>commons-beanutils</groupId>
            <artifactId>commons-beanutils</artifactId>
        </dependency>
        <!--jwt工具-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
        </dependency>

        <!--joda 时间工具类 -->
        <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
        </dependency>

5.1.2 复制yml配置

sc:
  jwt:
    secret: sc@Login(Auth}*^31)&czxy% # 登录校验的密钥
    pubKeyPath: D:/rsa/rsa.pub # 公钥地址
    priKeyPath: D:/rsa/rsa.pri # 私钥地址
    expire: 360 # 过期时间,单位分钟

5.1.3 拷贝配合类

  • 拷贝:JwtProperties

package com.czxy.zx.user.config;

import com.czxy.zx.user.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.1.4 拷贝工具类

  • 拷贝工具类

5.1.5 生成秘钥(可选)

  • 如果已经存在,此步省略

  • 编写测试类,生成公钥和私钥

    package com.czxy.zx.utils;
    
    /**
     * @author 桐叔
     * @email liangtong@itcast.cn
     */
    public class TestRsa {
        //公钥的位置
        private static final String pubKeyPath = "D:\\rsa\\rsa.pub";
        //私钥的位置
        private static final String priKeyPath = "D:\\rsa\\rsa.pri";
    
        public static void main(String[] args) throws Exception {
            RsaUtils.generateKey(pubKeyPath,priKeyPath,"1234");
        }
    
    }
    
    

5.2 登录成功:生成token

  • 准备工作已完成 

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

 @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 基本流程

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

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

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

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

     

5.3.2 查询详情

  • 修改 EduUserController 添加方法 

    @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 配置类 

  • 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 无效

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

6.作业:激活

6.1 分析

  • 需求:

    • 用户点击链接后可以进行账号激活,将用户的状态0改成1

    • 用户重复点击,提示“账号已经激活,无需重复激活”

    • 需要防止其他人帮着激活

    • 激活成功了,跳转到登录页面,“账号已激活,请登录”

    • 1天不激活,激活链接失效,需要重新发送

6.2 完善用户注册

@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:编写激活页面

<template>
  <div>用户激活页面</div>
</template>

<script>
import { active } from "@/api/user";
export default {
  methods: {
    async activeUserFn(uuid) {
      let baseResult = await active(uuid);
      this.$message.success(baseResult.message);
      this.$router.push('/login')
    }
  },
  mounted() {
    // 获得参数uuid
    let uuid = this.$route.query.uuid
    // 如果没有调整到登录页面
    if(! uuid) {
      this.$message.error('激活链接无效,请重新访问');
      this.$router.push('/login')
    } else {
      // 发送ajax进行激活
      this.activeUserFn(uuid);
    }
  },
}
</script>

<style>

</style>
  • 步骤2:编写ajax函数

//激活
export function active(uuid) {
  return axios.get(`/user-service/user/active?uuid=${uuid}`)
}

  • 步骤3:添加白名单

6.3.2 后端实现

 @GetMapping("/active")
    public BaseResult active(String uuid) {
        //1 使用uuid从redis获得信息
        String username = stringRedisTemplate.opsForValue().get(uuid);
        //2 判断,如果不存在给出提示
        if(username == null) {
            return BaseResult.ok("链接不完整or不需要激活or链接已超时");
        }
        //3 通过用户名查询用户
        QueryWrapper<EduUser> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("username", username);
        EduUser eduUser = eduUserService.getOne(queryWrapper);
        if(eduUser == null) {
            return BaseResult.ok("激活账号不存在");
        }
        //4 修改用户的状态 0 --> 1
        if(! "0".equals(eduUser.getStatus()) ) {
            return BaseResult.ok("账号不需要激活");
        }
        eduUser.setStatus("1");
        eduUserService.updateById(eduUser);
        //5 删除redis信息
        stringRedisTemplate.delete(uuid);
        //6 激活成功的提示
        return BaseResult.ok("激活成功");
    }

6.4 完善用户登录

@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";
            return BaseResult.ok("登录成功").append("token", token);
        }
        return BaseResult.error("用户名或密码不匹配");
    }
posted @ 2022-11-09 08:13  陶然同学  阅读(374)  评论(0编辑  收藏  举报