vue-element-admin-master 用户登录和注册+整合JWT

介绍

vue-element-admin 是一个后台前端解决方案,它基于 vue 和 element-ui实现。它使用了最新的前端技术栈,内置了 i18 国际化解决方案,动态路由,权限验证,提炼了典型的业务模型,提供了丰富的功能组件,它可以帮助你快速搭建企业级中后台产品原型。相信不管你的需求是什么,本项目都能帮助到你。

1. 后端用户服务

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

JavaBean

package com.czxy.zx.domain;

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

import java.util.Date;

  /**
   * @author txt
   * @email tantintong9968@163.com
   */
@Data
//@TableName("edu_user")
@ApiModel(value = "EduUser对象",description = "用户")
public class EduUser {

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

    @TableField(exist = false)
    @ApiModelProperty("验证码")
    private String verifycode;

    @TableField(exist = false)
    @ApiModelProperty("重复密码")
    private String repassword;

}

1.3 用户服务环境

  • 创建项目:zx-service-user31

  • 修改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
        password: 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 txt
 * @email tantintong9968@163.com
 */
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class UserServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserServiceApplication.class,args);
    }
}

1.4 用户服务基本模块

  • 拷贝配置类

    • MyBatisPlusConfig.java
    • Swagger2ConfigurationV3.java
  • 编写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 txt
     * @email tantintong9968@163.com
     */
    @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 txt
     * @email tantintong9968@163.com
     */
    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 txt
     * @email tantintong9968@163.com
     */
    @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 txt
     * @email tantintong9968@163.com
     */
    @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
        password: guest
        virtualHost: /
    
    
  • 拷贝工具类

    工具类

    package com.czxy.zx.utils;
    
    import org.springframework.mail.javamail.JavaMailSender;
    import org.springframework.mail.javamail.MimeMessageHelper;
    
    import javax.mail.internet.MimeMessage;
    
    /**
     * @author txt
     * @email tantintong9968@163.com
     */
    public class EmailUtils {
        /**
         * 发送邮件
         * @param javaMailSender
         * @param title 标题
         * @param email
         * @param text
         */
        public static void sendEmail(JavaMailSender javaMailSender, String title , String email, String text) {
            try {
                MimeMessage mimeMessage = javaMailSender.createMimeMessage();
                MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage,true);
                // a. 发件人
                mimeMessageHelper.setFrom("itcast_lt@126.com");
                // b. 收件人
                mimeMessageHelper.setTo(email);
                // c. 主题
                mimeMessageHelper.setSubject(title);
                // d. 正文
                mimeMessageHelper.setText(text, true);
                javaMailSender.send(mimeMessage);
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException(e.getMessage());
            }
        }
    }
    
    
  • 编写启动类

    package com.czxy.zx;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    /**
     * @author txt
     * @email tantintong9968@163.com
     */
    @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;

  /**
   * @author txt
   * @email tantintong9968@163.com
   */
@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 txt
   * @email tantintong9968@163.com
   */
@Data
public class UserEmail {
    private String username;
    private String email;
    private String text;
}

3.1.5 编写Rabbit配置类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Fy7yeYPN-1664327744645)(assets/image-20220425235913800.png)]

package com.czxy.zx.user.config;

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

  /**
   * @author txt
   * @email tantintong9968@163.com
   */
@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 txt
   * @email tantintong9968@163.com
   */
@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 拷贝配置类

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PvpLZghg-1664327744646)(assets/image-20220425235944490.png)]

package com.czxy.zx.config;

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

  /**
   * @author txt
   * @email tantintong9968@163.com
   */
@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 txt
   * @email tantintong9968@163.com
   */
@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.2.3 邮件总结

  • 邮件操作常见的协议:

    • SMTP ( Simple Mail Transfer Protocol )简单邮件传输协议 ,用于进行邮件发送的协议。
    • POP3 ( Post Office Protocol - Version 3 ) 邮局协议版本3 ,用于进行邮件接收的。

  • 每一个邮件服务提供商,都会明确自己每一个服务器的地址

  • yml配置

  • 发送邮件:

    • 发件人:from
    • 收件人:to (扩展:cc抄送、bcc暗送/密送)
    • 主题:subject
    • 正文:content/text

3.3 后端:用户登录

3.3.0 前端登录流程

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

  • 步骤二:登录调用 vuex

  • 步骤三:查看vuex

    • 将用户的完整信息,传递给ajax方法

  • 步骤四:调用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.3 后端实现

  • 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 txt
   * @email tantintong9968@163.com
   */
@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);
        }
    

3.5 总结

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 tanxintong.
 */
@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;

  /**
   * @author txt
   * @email tantintong9968@163.com
   */
@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 txt
     * @email tantintong9968@163.com
     */
    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 txt
     * @email tantintong9968@163.com
     */
    @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("register" + eduUser.getUsername());
        stringRedisTemplate.delete("register" + 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="verifycodeImg" @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',
      verifycodeImg: '',
    }
  },
  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.verifycodeImg = `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 概述

  • 什么是JWT?
    • JWT( json-web-token ): 现在比较火的token中的一种,为了解决HTTP协议无状态的问题。
  • 采用的方法:提供一个有效的标识数据
    • 是否有标识数据?
    • 标识数据是否有效?

5.0 分析

5.1 搭建环境

5.1.1 拷贝坐标(已有)

  • 在common项目中已经添加
        <!--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配置

  • 给user服务的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 txt
   * @email tantintong9968@163.com
   */
@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 txt
     * @email tantintong9968@163.com
     */
    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

  • 在gateway 网关中,添加如下配置:
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/active
      - /verifycode

5.4.2 pom和工具类

  • 添加坐标

            <!--自定义项目-->
            <dependency>
                <groupId>com.czxy.zx</groupId>
                <artifactId>zx-domain-java12</artifactId>
            </dependency>
    
            <!--jwt工具-->
            <dependency>
                <groupId>io.jsonwebtoken</groupId>
                <artifactId>jjwt</artifactId>
            </dependency>
            <!--joda 时间工具类 -->
            <dependency>
                <groupId>joda-time</groupId>
                <artifactId>joda-time</artifactId>
            </dependency>
            <!--JavaBean工具类,用于JavaBean数据封装-->
            <dependency>
                <groupId>commons-beanutils</groupId>
                <artifactId>commons-beanutils</artifactId>
            </dependency>
    
  • jwt相关的工具类

5.4.3 配置类

  • FilterProperties

    package com.czxy.zx.config;
    
    import lombok.Data;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.context.annotation.Configuration;
    
    import java.util.List;
    
    /**
     * @author txt
     * @email tantintong9968@163.com
     */
    @Data
    @Configuration
    @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 txt
     * @email tantintong9968@163.com
     */
    @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.4 过滤器

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 txt
   * @email tantintong9968@163.com
   */
@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 无效

  • 如果token实现,需要给用户提示

  • 编写ajax响应的拦截器

if(error.response.status == 401) {
      // 401 提示信息,重新登录
      MessageBox.confirm('Token失效,请重新登录', '确认退出', {
        confirmButtonText: '重新登录',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        store.dispatch('user/resetToken').then(() => {
          location.reload()
        })
      })
    } else {
      // 普通提示信息
      Message({
        message: error.message,
        type: 'error',
        duration: 5 * 1000
      })
    }

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("用户名或密码不匹配");
    }

7. 退出登录

  • 前端

  • 后端

end

  • 7
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 11
    评论
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

♚焕蓝·未来

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值