手把手教你入门vue+springboot开发(三)--登录功能后端

文章目录

  • 前言
  • 一、redis安装
  • 二、后端代码
    • 1.修改application.yml文件
    • 2.增加utils文件
    • 3.增加Result类
    • 4.修改UserController类
    • 5.修改UserMapper类
    • 6.修改UserService和UserServiceImpl类
    • 7.增加LoginInterceptor类
    • 8.增加WebConfig类
    • 9.修改pom.xml文件


前言

前两篇我们用vue+springboot框架实现了一个很简单的查询数据的demo,通过这个demo主要是熟悉IDEAU和VSCode等工具的使用,以及熟悉vue+springboot前后端分离开发的基本流程。接下来两篇我们将在前面的基础上实现登录这个B/S典型应用场景,进一步深入的理解B/S架构、vue的一些基础插件、前后端交互过程。


一、redis安装

1)首先下载redis压缩包Redis-x64-3.0.504.zip,地址如下:
https://github.com/MicrosoftArchive/redis/releases
2)解压到本地路径C:\Program Files,进入解压后Redis-x64-3.0.504目录,执行以下命令:
redis-server.exe redis.windows.conf
如下图所示表示redis启动成功。
在这里插入图片描述
先把数据库中password改成md5加密,如下图,admin密码为:12345678,数据库中改成它的md5码:25d55ad283aa400af464c76d713c07ad
在这里插入图片描述

二、后端代码

1.修改application.yml文件

spring配置字段下增加以下内容连接redis服务器。

data:
    redis:
      host: localhost
      port: 6379

2.增加utils文件

增加utils包,里面增加JwtUtil、Md5Util、ThreadLocalUtil类,增加JWT、MD5和线程局部变量方法。
JwtUtil类代码:

package com.example.demo.utils;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;

import java.util.Date;
import java.util.Map;

public class JwtUtil {

    private static final String KEY = "itheima";

    //接收业务数据,生成token并返回
    public static String genToken(Map<String, Object> claims) {
        return JWT.create()
                .withClaim("claims", claims)
                .withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 60 * 60 ))
                .sign(Algorithm.HMAC256(KEY));
    }

    //接收token,验证token,并返回业务数据
    public static Map<String, Object> parseToken(String token) {
        return JWT.require(Algorithm.HMAC256(KEY))
                .build()
                .verify(token)
                .getClaim("claims")
                .asMap();
    }

}

Md5Util类代码:

package com.example.demo.utils;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class Md5Util {
    /**
     * 默认的密码字符串组合,用来将字节转换成 16 进制表示的字符,apache校验下载的文件的正确性用的就是默认的这个组合
     */
    protected static char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

    protected static MessageDigest messagedigest = null;

    static {
        try {
            messagedigest = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException nsaex) {
            System.err.println(Md5Util.class.getName() + "初始化失败,MessageDigest不支持MD5Util。");
            nsaex.printStackTrace();
        }
    }

    /**
     * 生成字符串的md5校验值
     *
     * @param s
     * @return
     */
    public static String getMD5String(String s) {
        return getMD5String(s.getBytes());
    }

    /**
     * 判断字符串的md5校验码是否与一个已知的md5码相匹配
     *
     * @param password  要校验的字符串
     * @param md5PwdStr 已知的md5校验码
     * @return
     */
    public static boolean checkPassword(String password, String md5PwdStr) {
        String s = getMD5String(password);
        return s.equals(md5PwdStr);
    }


    public static String getMD5String(byte[] bytes) {
        messagedigest.update(bytes);
        return bufferToHex(messagedigest.digest());
    }

    private static String bufferToHex(byte bytes[]) {
        return bufferToHex(bytes, 0, bytes.length);
    }

    private static String bufferToHex(byte bytes[], int m, int n) {
        StringBuffer stringbuffer = new StringBuffer(2 * n);
        int k = m + n;
        for (int l = m; l < k; l++) {
            appendHexPair(bytes[l], stringbuffer);
        }
        return stringbuffer.toString();
    }

    private static void appendHexPair(byte bt, StringBuffer stringbuffer) {
        char c0 = hexDigits[(bt & 0xf0) >> 4];// 取字节中高 4 位的数字转换, >>>
        // 为逻辑右移,将符号位一起右移,此处未发现两种符号有何不同
        char c1 = hexDigits[bt & 0xf];// 取字节中低 4 位的数字转换
        stringbuffer.append(c0);
        stringbuffer.append(c1);
    }
}

ThreadLocalUtil类代码:

package com.example.demo.utils;

import java.util.HashMap;
import java.util.Map;

/**
 * ThreadLocal 工具类
 */
@SuppressWarnings("all")
public class ThreadLocalUtil {
    //提供ThreadLocal对象,
    private static final ThreadLocal THREAD_LOCAL = new ThreadLocal();

    //根据键获取值
    public static <T> T get(){
        return (T) THREAD_LOCAL.get();
    }

    //存储键值对
    public static void set(Object value){
        THREAD_LOCAL.set(value);
    }


    //清除ThreadLocal 防止内存泄漏
    public static void remove(){
        THREAD_LOCAL.remove();
    }
}

3.增加Result类

bean增加Result类,定义HTTP返回结果格式。

package com.example.demo.bean;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

//统一响应结果
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Result<T> {
    private Integer code;//业务状态码  0-成功  1-失败
    private String message;//提示信息
    private T data;//响应数据

    //快速返回操作成功响应结果(带响应数据)
    public static <E> Result<E> success(E data) {
        return new Result<>(0, "操作成功", data);
    }

    //快速返回操作成功响应结果
    public static Result success() {
        return new Result(0, "操作成功", null);
    }

    public static Result error(String message) {
        return new Result(1, message, null);
    }
}

4.修改UserController类

selectAll方法的返回值需要使用Result定义格式,参照下面代码改一下,然后增加login方法处理"/user/login"请求,校验用户名、密码后生成JWT令牌,并保存在redis中,当其它请求过来时需要验证JWT令牌,同时令牌中还保存了用户名和密码等信息,当需要这些信息时就不用再查询mysql数据库。

 @Autowired
    private StringRedisTemplate stringRedisTemplate;

   @PostMapping("/login")
    public Result<String> login(@Pattern(regexp = "^\\S{5,16}$") String username, @Pattern(regexp = "^\\S{5,16}$") String password) {
        //根据用户名查询用户
        User loginUser = userService.findByUserName(username);
        //判断该用户是否存在
        if (loginUser == null) {
            return Result.error("用户名错误");
        }

        //判断密码是否正确  loginUser对象中的password是密文
        if (Md5Util.getMD5String(password).equals(loginUser.getPassword())) {
            //登录成功
            Map<String, Object> claims = new HashMap<>();
            claims.put("id", loginUser.getId());
            claims.put("username", loginUser.getUserName());
            String token = JwtUtil.genToken(claims);
            //把token存储到redis中
            ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();
            operations.set(token,token,1, TimeUnit.HOURS);
            return Result.success(token);
        }
        return Result.error("密码错误");
    }

增加后有些字段会是红色,工具支持自动导入相关的类,鼠标放在字段上会出现下图提示,选择“Import class”即可。
在这里插入图片描述
注意Pattern字段工具自动导入的类是
import org.intellij.lang.annotations.Pattern;
这里regexp可能识别不到,那就换成下面的类,但需要增加spring-boot-starter-validation依赖
import jakarta.validation.constraints.Pattern;

5.修改UserMapper类

增加findByUserName方法。

    //根据用户名查询用户
    @Select("select * from users where userName=#{username}")
    User findByUserName(String username);

6.修改UserService和UserServiceImpl类

UserService类增加:

    //根据用户名查询用户
    User findByUserName(String username);

UserServiceImpl类增加:

    @Override
    public User findByUserName(String username) {
        User u = userMapper.findByUserName(username);
        return u;
    }

7.增加LoginInterceptor类

增加interceptors包,里面增加LoginInterceptor类,这里主要通过拦截器处理校验令牌的过程。

@Component
public class LoginInterceptor implements HandlerInterceptor {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //处理OPTIONS请求时,返回正确的CORS头部信息
        if (request.getMethod().equals("OPTIONS")) {
            response.setHeader("Access-Control-Allow-Origin", "*");//*表示放行所有的源
            response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, HEAD, OPTIONS");
            response.setHeader("Access-Control-Allow-Headers", "*");
            response.setHeader("Access-Control-Allow-Credentials", "true");
            response.setStatus(HttpServletResponse.SC_OK);
            return false;
        }
        else {
            //令牌验证
            String token = request.getHeader("Authorization");
            //验证token
            try {
                //从redis中获取相同的token
                ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();
                String redisToken = operations.get(token);
                if (redisToken == null) {
                    //token已经失效了
                    throw new RuntimeException();
                }
                Map<String, Object> claims = JwtUtil.parseToken(token);

                //把业务数据存储到ThreadLocal中
                ThreadLocalUtil.set(claims);
                //放行
                return true;
            } catch (Exception e) {
                //http响应状态码为401
                response.setStatus(401);
                //不放行
                return false;
            }
        }
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //清空ThreadLocal中的数据
        ThreadLocalUtil.remove();
    }
}

这里调试的时候出现跨域访问不了后端/user/selectAll的情况,所以需要加上OPTIONS这段代码。

8.增加WebConfig类

增加config包,里面增加WebConfig类,如果不需要拦截可以在这里设置。

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //登录接口不拦截
        registry.addInterceptor(loginInterceptor).excludePathPatterns("/user/login");
    }

    //解决跨域问题
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**").allowedOriginPatterns("*")
                .allowedMethods("GET", "HEAD", "POST","PUT", "DELETE", "OPTIONS")
                .allowCredentials(true).maxAge(3600);
    }
}

解决跨域问题可以直接在这里增加addCorsMappings方法,这样就不需要在每个controller类中增加@CrossOrigin。

9.修改pom.xml文件

增加上面代码需要的依赖。

<!--redis坐标-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
<!--lombok依赖-->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
    </dependency>
<!--java-jwt坐标-->
    <dependency>
      <groupId>com.auth0</groupId>
      <artifactId>java-jwt</artifactId>
      <version>4.4.0</version>
    </dependency>
<!--validation依赖-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>

下一篇我们继续实现前端代码

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/710026.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于负相关误差函数的4集成BP神经网络matlab建模与仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 (完整程序运行后无水印) 2.算法运行软件版本 MATLAB2022a 3.部分核心程序 ...............................................................…

XILINX 7系列XDMA使用_IP核介绍以及工程搭建

文章目录 一、XDMA IP核1.1、接口说明1.2、配置页说明 二、XDMA工程搭建2.1、BD搭建2.2 Linux下XDMA驱动安装2.3 Linux下使用XDMA进行数据传输 一、XDMA IP核 1.1、接口说明 sys_clk&#xff1a;主机给PCIE提供的时钟信号&#xff0c;通过原理图查看 sys_rst_n&#xff1a;主机…

宠物健康顾问系统的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;顾问管理&#xff0c;用户管理&#xff0c;健康知识管理&#xff0c;管理员管理&#xff0c;论坛管理&#xff0c;公告管理 顾问账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;顾…

宝藏速成秘籍(3)选择排序法

一、前言 1.1、概念 选择排序法&#xff08;Selection Sort&#xff09;是一种简单直观的排序算法。它的基本思想是&#xff1a;每次从待排序的数组中选择最小&#xff08;或最大&#xff09;的元素&#xff0c;将其放在已排序部分的末尾&#xff0c;直到所有元素都排序完毕。…

资源分享—2021版乡级制图规范符号库

汇总整理超图平台软件相关的各类资源&#xff08;包括但不限于符号库、地图模板、地理处理模型等&#xff09;&#xff0c;助力项目的高效制图、提高数据生产效率等业务。 本次分享新版国土空间规划【2021版乡级制图规范符号库】&#xff0c;提供SuperMap格式符号库下载。 符…

简单了解CPU的工作原理

目录 一、基本结构以及对应功能 &#xff08;1&#xff09;基本结构 &#xff08;2&#xff09;几个重要寄存器的详细介绍 操作码 (Opcode) 操作数 (Operands) 指令表 (Instruction Table) 第一个&#xff1a;程序计数器 (PC) 第二个&#xff1a;指令寄存器 (IR&#x…

头歌资源库(3)爬楼梯问题

一、 问题描述 二、算法思想 假设要爬上n阶楼梯&#xff0c;我们可以将问题分解为子问题&#xff1a;爬到第n-1阶楼梯的方法数加上爬到第n-2阶楼梯的方法数加上爬到第n-3阶楼梯的方法数。 设f(n)表示爬到第n阶楼梯的方法数&#xff0c;则有递推关系&#xff1a;f(n) f(n-1…

有声读物管理平台Booksonic-Air

老苏最近在听评书&#xff0c;所以想找个软件来管理和收听&#xff0c;找了一圈&#xff0c;感觉 Booksonic-Air 可能能满足老苏的需求。 什么是 Booksonic-Air &#xff1f; Booksonic-Air 是一个用于流式传输有声读物的服务器&#xff0c;是原始 Booksonic 服务器的后继者。…

windows上运行arm32架构的安卓模拟器

说明 主要功能&#xff1a;在win10上研究和学习32位arm汇编指令的执行 环境如下 主机环境: windows10 目标模拟器环境:armeabi-v7a调试环境搭建 1、下载android studio [下载地址](https://developer.android.com/studio?hlzh-cn) ![在这里插入图片描述](https://img-blog…

RedHat9 | Mariadb数据库的配置与管理

一、实验环境 1、Mariadb数据库介绍 MariaDB数据库管理系统是一个开源的关系型数据库管理系统&#xff0c;与MySQL高度兼容&#xff0c;并提供了更多的功能和性能优化。 起源和背景 MariaDB是MySQL的一个分支&#xff0c;主要由开源社区维护。由MySQL的创始人Michael Widen…

【面试干货】Java集合类详解:List、Set、Queue、Map、Stack的特点与用法

【面试干货】Java集合类详解&#xff1a;List、Set、Queue、Map、Stack的特点与用法 1、Map1.1 特点1.2 用法1.3 常见的实现类 2、Set2.1 特点2.2 用法2.3 常见的实现类 3、List3.1 特点3.2 用法3.3 常见的实现类 4、Queue4.1 特点4.2 用法4.3 常见的实现类 5、Stack5.1 特点5.…

Springboot实现微信小程序登录功能

目录 一 什么是微信登录功能 二 实现微信登录功能的整体逻辑 三 微信登录功能实现步骤 一 什么是微信登录功能 微信小程序登录功能一般用于开发微信小程序的时候&#xff0c;我们需要使用微信授权登录我们的微信小程序&#xff0c;本篇博客就微信小程序实现微信授权登录以及s…

基于LangChain-Chatchat实现的RAG-本地知识库的问答应用[2]-简洁部署版

基于LangChain-Chatchat实现的RAG-本地知识库的问答应用[2]-简洁部署版 1.环境要求 1.1 软件要求 要顺利运行本代码,请按照以下系统要求进行配置 已经测试过的系统 Linux Ubuntu 22.04.5 kernel version 6.7其他系统可能出现系统兼容性问题。 最低要求 该要求仅针对标准模…

OpenStack入门体验及一键部署

OpenStack入门体验 技能目标&#xff1a; 了解云计算概念 了解OpenStack 了解OpenStack的构成 会OpenStack单机环境一键部署 从控制台认识OpenStack各项功能会 通过OpenStack控制台创建云主机 什么是云计算 云计算(cloudcomputing)是一种基于网络的超级计算模式&a…

Docker安装Nginx(各种错误版)

Docker安装-CSDN博客 安装启动Docker之后 docker run -d -p 81:81 --name nginx nginx 这样没有指定版本 docker run&#xff1a;启动一个新的容器。-d&#xff1a;以分离模式运行容器&#xff08;后台运行&#xff09;。-p 81:81&#xff1a;将主机的 81 端口映射到容器的 …

用Vue3和p5.js打造一个交互式数据可视化仪表盘

本文由ScriptEcho平台提供技术支持 项目地址&#xff1a;传送门 基于 Vue.js 集成 p5.js 实现交互式波形图 应用场景介绍 在数据可视化领域&#xff0c;波形图广泛应用于展示动态变化的数据&#xff0c;如声音信号、心跳曲线等。通过动态绘制波形图&#xff0c;用户可以直观…

网络标准架构--OSI七层、四层

OSI七层网络架构&#xff0c;以及实际使用的四层网络架构。

细说ARM MCU的串口发送数据的实现过程

目录 1、条件及工程配置 2、实现串口发送的库函数 3、修改whlie(1)中的代码 4、修改回调函数 5、下载运行 前面的文章介绍了用串口的接收中断来接收数据&#xff0c;本文介绍通过串口从MCU向外发送数据。 1、条件及工程配置 文章依赖的硬件及工程配置同本文作者的其他文…

热门开源项目推荐:智谱GLM-4-9B和ChatGLM3-6B

目录 热门开源项目推荐&#xff1a;智谱GLM-4-9B和ChatGLM3-6B 1.引言 1.1 开源文化简介 1.2 开源项目的重要性 1.3 博客目的和读者价值 2.什么是开源项目&#xff1f; 2.1 开源定义 2.2 开源许可证类型 2.3 开源社区的作用 3.为什么程序员应该关注开源项目&#xff…

javaWeb项目-ssm+jsp学生请假系统功能介绍

本项目源码:java-ssm-jsp学生请假系统源码说明文档资料资源-CSDN文库 项目关键技术 开发工具&#xff1a;IDEA 、Eclipse 编程语言: Java 数据库: MySQL5.7 框架&#xff1a;ssm、Springboot 前端&#xff1a;Vue、ElementUI 关键技术&#xff1a;springboot、SSM、vue、MYSQL…