day03_登录注销(前端接入登录,异常处理, 图片验证码,获取用户信息接口,退出功能)

文章目录

  • 1. 前端接入登录
    • 1.1 修改前端代码
    • 1.2 跨域请求
      • 1.2.1 跨域请求简介
      • 1.2.2 COSR概述
        • CORS简介
        • CORS原理
      • 1.2.3 CORS解决跨域
  • 2. 异常处理
    • 2.1 提示空消息分析
    • 2.2 系统异常分类
    • 2.3 异常处理
      • 2.2.1 方案一
      • 2.2.2 方案二
  • 3. 图片验证码
    • 3.1 图片验证码意义
    • 3.2 实现思路
    • 3.3 后端接口编写
      • 3.3.1 实体类创建
      • 3.3.2 IndexController
      • 3.3.3 ValidateCodeService
    • 3.4 前端接入
      • 3.4.1 实现思路
      • 3.4.3 代码实现
        • 页面表单项
        • 规则校验
        • api/login.js
        • onMounted
    • 3.5 校验验证码
  • 4. 获取用户信息接口
    • 4.1 前端源码分析
      • 4.1.1 请求发送分析
      • 3.1.2 用户信息使用
      • 3.1.3 token传递
    • 4.2 后端接口
      • 4.2.1 IndexController
      • 4.2.2 SysUserService
    • 4.3 前端接入
    • 4.4 进入首页
  • 5. 退出功能
    • 5.1 需求分析
    • 5.2 代码实现
      • 5.2.1 后端接口
        • IndexController
        • SysUserService
      • 5.2.2 前端接入
        • login.js
        • Userinfo.vue
        • Userinfo.vue

1. 前端接入登录

当后端接口开发好了以后就可以让前端去请求该登录接口完成登录操作。

1.1 修改前端代码

修改src/utils/request.js更改基础请求路径

const service = axios.create({
  // 后端服务的ip地址和端口号
  baseURL: 'http://localhost:8503',    
  timeout: 10000,
  withCredentials: true,
})

修改src/api/login.js更改登录接口地址

// 登录接口
export const Login = data => {
  return request({
    url: '/admin/system/index/login',
    method: 'post',
    data,
  })
}

发送登录请求,那么此时会报一个错误:

在这里插入图片描述

报错的原因是因为此时的请求是一个跨域的请求。

1.2 跨域请求

1.2.1 跨域请求简介

跨域请求:通过一个域的JavaScript脚本和另外一个域的内容进行交互

域的信息:协议、域名、端口号

在这里插入图片描述

同域:当两个域的协议、域名、端口号均相同

如下所示:

在这里插入图片描述

同源【域】策略:在浏览器中存在一种安全策略就是同源策略,同源策略(Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本的安全功

能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实

现。同源策略会阻止一个域的javascript脚本和另外一个域的内容进行交互。

1.2.2 COSR概述

CORS简介

官网地址:https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS

CORS的全称为Cross-origin Resource Sharing,中文含义是跨域资源共享,CORS 给了web服务器一种权限:服务器可以选择是否允许跨域请求访

问到它们的资源。

CORS原理

浏览器将CORS请求分成两类:简单请求非简单请求。怎么区分这两者呢?

简单请求

我们先来看两个条件:

(1)HTTP请求方法是以下三种之一:
	·HEAD
	·GET
	·POST
(2)只包含简单HTTP请求头,即:
	·Accept,
	·Accept-Language,
	·Content-Language,
	·Content-Type并且值是 application/x-www-form-urlencoded, multipart/form-data, 或者 text/plain之一的(忽略参数)。

当请求满足上面的两个条件时,则该请求被视为简单请求,否则被视为非简单请求。简单请求与非简单请求的最主要区别就是跨域请求是否需要发送预检请求(preflight request)。

简单请求的跨域请求响应流程:

在这里插入图片描述

在进行跨域请求时,如果是简单请求,则浏览器会在请求中增加一个Origin请求头之后直接发送CORS请求,服务器检查该请求头的值是否在服务器设置的CORS许可范围内,如果在许可范围内,则服务器同意本次请求,如果不在许可范围内,则服务会返回一个没有包含Access-Control-Allow-

Origin 响应头的HTTP响应。

非简单请求

非简单请求的跨域请求响应流程:

在这里插入图片描述

除了简单请求其他的请求都是非简单请求,非简单请求会先发送一次预检请求**(OPTIONS请求),浏览器除了会带上Origin请求头**之外,还会再带

Access-Control-Request-Method 和 Access-Control-Request-Headers 这两个请求头,服务器在收到预检请求之后,会检查这三个请

求头是否与服务器的资源设置(接口)一致,如服务器的接口只允许请求方法为GET、Origin为http://www.abc.com:8080、Access-Control-Request-Header 为 content-type的请求,只要预检请求中三个请求头有任意一个值与服务器的资源(接口)设置不一致,服务器就会拒绝预检请求,

如果都一致,则服务器确认通过预检请求并返回带有Access-Control-Allow-Credentials、Access-Control-Allow-Headers、Access-Control-Allow-Methods、Access-Control-Allow-Origin、Access-Control-Max-Age【间隔多长时间在发起预检请求】等响应头的相应。当预检请求通过以后此时

就可以发送真实请求。

1.2.3 CORS解决跨域

后端服务器开启跨域支持:

方案一:在IndexController上添加**@CrossOrigin**注解

@RestController
@RequestMapping(value = "/admin/system/index")
@CrossOrigin(allowCredentials = "true" , originPatterns = "*" , allowedHeaders = "*") // maxAge默认值是30min
public class IndexController {

}

弊端:每一个controller类上都来添加这样的一个接口影响开发效率、维护性较差

方案二:添加一个配置类配置跨域请求

// com.atguigu.spzx.manager.config
@Component
public class WebMvcConfiguration implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")// 添加路径规则
                .allowCredentials(true) // 是否允许在跨域的情况下传递Cookie
                .allowedOriginPatterns("*") // 允许请求来源的域规则
            	.allowedMethods("*")
                .allowedHeaders("*") ;  // 允许所有的请求头
    }
    
}

2. 异常处理

2.1 提示空消息分析

现象说明:当用户输入的用户名或者密码错误,前端页面提示空信息

问题分析:当用户名或者密码输入错误以后,此时后端服务器抛出了异常。但是在后端并没有对异常进行处理,此时就会给前端响应异常信息,在前端提供了axios的响应拦截器,那么通过axios响应拦截器拦截了异常信息,然后给出为空的提示信息。

源码查看:src/utils/request.js

// 拦截响应
service.interceptors.response.use(
  // 响应成功进入第1个函数,该函数的参数是响应对象
  response => {
    return response.data
  },
  // 响应失败进入第2个函数,该函数的参数是错误对象
  async error => {  
    ...
    try {
      ElMessage.error(error.response.data.msg)		// 打印错误信息
    } catch (err) {
      ElMessage.error(error.message)				// 打印错误信息
    }
    return Promise.reject(error)
  }
)

2.2 系统异常分类

在项目中为了更加详情的对异常出现的异常问题进行排查,那么此时应该对异常进行区分,大致可以分为如下两种异常:

1、系统异常:一般由框架本身所抛出的异常:NullPointerException、IllegalArgumentException、ConnectTimeoutException…

2、业务异常:业务异常就是对我们的业务错误进行描述的异常,往往需要进行自定义。常见的业务错误:用户名或者密码错误、用户名重复…

自定异常:

// com.atguigu.spzx.common.exception
@Data
public class GuiguException extends RuntimeException {

    private Integer code ;          // 错误状态码
    private String message ;        // 错误消息
    //真实异常
    private Throwable e;
	// 封装错误状态码和错误消息
    private ResultCodeEnum resultCodeEnum ;     

    public GuiguException(ResultCodeEnum resultCodeEnum,Throwable e) {
        this.resultCodeEnum = resultCodeEnum ;
        this.code = resultCodeEnum.getCode() ;
        this.message = resultCodeEnum.getMessage();
        this.e = e;
    }

    public GuiguException(Integer code , String message,Throwable e) {
        this.code = code ;
        this.message = message ;
        this.e = e;
    }

}

更改异常的抛出代码:

// com.atguigu.spzx.manager.service.impl.SysUserServiceImpl#login
SysUser sysUser = sysUserMapper.selectByUserName(loginDto.getUserName());
if(sysUser == null) {
    throw new GuiguException(ResultCodeEnum.SUCCESS,null) ;		// 抛出自定义的业务异常
}

// 验证密码是否正确
String inputPassword = loginDto.getPassword();
String md5InputPassword = DigestUtils.md5DigestAsHex(inputPassword.getBytes());	// 抛出自定义的业务异常
if(!md5InputPassword.equals(sysUser.getPassword())) {
    throw new GuiguException(ResultCodeEnum.LOGIN_ERROR,null) ;
}

2.3 异常处理

要解决上述问题,那么此时就需要对异常进行处理。统一向前端响应200的http的状态码,然后通过不同的业务状态码区分登录成功还是失败。

2.2.1 方案一

在controller方法中使用try…catch捕获业务层方法所抛出的异常。如下所示:

// IndexController#login方法
@PostMapping(value = "/login")
public Result<LoginVo> login(@RequestBody LoginDto loginDto) {
    try {
        LoginVo loginVo = sysUserService.login(loginDto) ;
        return Result.ok().data(loginVo) ;
    }catch (GuiguException exception) {
        return Result.error() ;
    }
}

2.2.2 方案二

使用spring mvc的全局异常处理器进行异常的处理,整体的工作流程如下所示:

在这里插入图片描述

开发一个全局异常处理器:

// com.atguigu.spzx.common.exception
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    @ExceptionHandler(value = GuiguException.class)     // 处理自定义异常
    public Result guiguExceptionHandler(GuiguException exception) {
        if(e.getE()!=null){//获取真实异常信息打印
            //打印异常堆栈日志
            //org.apache.commons.lang3.exception.ExceptionUtils
            log.error(ExceptionUtils.getStackTrace(e.getE()));
        }
        return Result.error().code(e.getCode()).message(e.getMessage()) ;
    }

    @ExceptionHandler(value = Exception.class)          // 处理系统异常
    public Result systemExceptionHandler(Exception exception) {
        if(e!=null){
            //打印异常堆栈日志
    //org.apache.commons.lang3.exception.ExceptionUtils
            log.error(ExceptionUtils.getStackTrace(e));
        }
        return Result.error();
    }

}

在spzx-manager中使用全局异常处理器:

方式一:在启动类上使用@Import注解导入全局异常处理器到spring容器中

@Import(value = GlobalExceptionHandler.class)

方式二:自定义注解对@Import注解进行封装,然后在启动类上使用自定义注解

// com.atguigu.spzx.common.anno
@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = ElementType.TYPE)
@Import(value = GlobalExceptionHandler.class)
public @interface EnableGlobaleExceptionHandler {		// 启动类上添加该注解

}

方式三:使用spring boot3的自动化配置完成全局异常处理器的自动化配置

步骤:

1、在common-service模块中的resources目录下创建一个META-INF/spring文件夹,在该文件夹下创建一个文件,名称为:

org.springframework.boot.autoconfigure.AutoConfiguration.imports

2、在该文件中添加全局异常处理器的全类名

com.atguigu.spzx.common.exception.GlobalExceptionHandler

3. 图片验证码

3.1 图片验证码意义

验证码是全自动区分计算机和人类的图灵测试的缩写,是一种区分用户是计算机还是人的公共全自动程序,可以防止恶意破解密码、刷票、论坛灌水,有效防止某个黑客对某一个特定注册用户用特定程序暴力破解方式进行不断的登录尝试。

页面效果如下所示:

在这里插入图片描述

3.2 实现思路

整体的实现思路,如下图所示:

在这里插入图片描述

3.3 后端接口编写

3.3.1 实体类创建

创建一个实体类封装,给前端返回的验证码数据:

// com.atguigu.spzx.model.vo.system;
@Data
public class ValidateCodeVo {

    private String codeKey ;        // 验证码的key
    private String codeImage ;      // 图片验证码对应的字符串数据

}

3.3.2 IndexController

在IndexController中添加获取验证码接口方法:

// com.atguigu.spzx.manager.controller.IndexController
@GetMapping(value = "/generateValidateCode")
public Result<ValidateCodeVo> generateValidateCode() {
    ValidateCodeVo validateCodeVo = validateCodeService.generateValidateCode();
    return Result.build(validateCodeVo , ResultCodeEnum.SUCCESS) ;
}

3.3.3 ValidateCodeService

业务层代码实现:

// com.atguigu.spzx.manager.service
public interface ValidateCodeService {

    // 获取验证码图片
    public abstract ValidateCodeVo generateValidateCode();

}

// com.atguigu.spzx.manager.service.impl
@Service
public class ValidateCodeServiceImpl implements ValidateCodeService {

    @Autowired
    private StringRedisTemplate<String , String> stringRedisTemplate ;

    @Override
    public ValidateCodeVo generateValidateCode() {

        // 使用hutool工具包中的工具类生成图片验证码
        CircleCaptcha circleCaptcha = CaptchaUtil.createCircleCaptcha(150, 48, 4, 20);
        String codeValue = circleCaptcha.getCode();
        String imageBase64 = circleCaptcha.getImageBase64();

        // 生成uuid作为图片验证码的key
        String codeKey = UUID.randomUUID().toString().replace("-", "");

        // 将验证码存储到Redis中
        stringRedisTemplate.opsForValue().set("user:login:validatecode:" + codeKey , codeValue , 5 , TimeUnit.MINUTES);

        // 构建响应结果数据
        ValidateCodeVo validateCodeVo = new ValidateCodeVo() ;
        validateCodeVo.setCodeKey(codeKey);
        //图片验证码 base64字符串前拼接data:image/png;base64, img标签可以解析展示
        validateCodeVo.setCodeImage("data:image/png;base64," + imageBase64);

        // 返回数据
        return validateCodeVo;
    }

}

使用postman进行测试。

3.4 前端接入

3.4.1 实现思路

整体实现思路:

1、登录表单中添加验证码表单项,绑定对应的数据模型(可以问GPT)

2、添加验证码输入框校验规则

3、在api/login.js中添加请求后端获取验证码接口方法

4、在首页中使用vue的onMounted钩子函数发送请求获取图片验证码

3.4.3 代码实现

页面表单项
<!-- 页面结构 -->
<el-form-item prop="code">
    <div class="code">
        <el-input
                  class="text"
                  v-model="model.code"
                  prefix-icon="Picture"
                  placeholder="请输入验证码"
                  ></el-input>
        <img :src="codeImage" @click="refreshCodeImage" />
    </div>
</el-form-item>

css样式:

// 验证码输入框样式 start
.code {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 10px;
}

.code img {
  cursor: pointer;
  margin-left: 20px;
}
// 验证码输入框样式 end
规则校验

更改views/login/index.vue页面的vue规则校验代码

const getRules = () => ({
    captcha: [
        {
            required: true,
            message: '验证码不能为空',
            trigger: 'blur',
        },
    ],
})
api/login.js

在api/login.js中添加请求后端获取验证码接口方法

// 获取验证码
export const GetValidateCode = () => {
  return request({
    url: "/admin/system/index/generateValidateCode",
    method: 'get'
  })
}
onMounted

在首页中使用vue的onMounted钩子函数发送请求获取图片验证码

import { onMounted } from 'vue'
import { Login , GetValidateCode } from '@/api/login'
export default defineComponent({
  setup() {
    // onMounted钩子函数
    onMounted(() => {
      state.refreshCaptcha()
    })

    const state = reactive({
      model: {
        userName: 'admin',
        password: '111111',
        code: '',      // 用户输入的验证码
        codeKey: ''       // 后端返回的验证码key
      },
      codeImage: "" ,
      refreshCodeImage: async () => {
          const { data } = await GetValidateCode() ;
          state.model.codeKey = data.codeKey
          state.codeImage = data.codeImage
      }
    })

    return {
      ...toRefs(state),
    }
  },
})
</script>

3.5 校验验证码

对之前的登录方法进行修改,添加校验验证码的逻辑代码。

步骤:

1、实体类修改

// com.atguigu.spzx.model.dto.system
@Data
public class LoginDto {

    private String userName ;
    private String password ;
    private String code ;
    private String codeKey ;

}

2、SysUserServiceImpl登录方法修改

// com.atguigu.spzx.manager.service.impl.SysUserServiceImpl#login
// 校验验证码是否正确
String code = loginDto.getCode();     // 用户输入的验证码
String codeKey = loginDto.getCodeKey();     // redis中验证码的数据key

// 从Redis中获取验证码
String redisCode = stringRedisTemplate.opsForValue().get("user:login:validatecode:" + codeKey);
if(StrUtil.isEmpty(redisCode) || !StrUtil.equalsIgnoreCase(redisCode , code)) {
    throw new GuiguException(ResultCodeEnum.VALIDATE_CODE_ERROR) ;
}

// 验证通过删除redis中的验证码
stringRedisTemplate.delete("user:login:validatecode:" + codeKey) ;

// ResultCodeEnum类添加如下枚举项
VALIDATE_CODE_ERROR(202 , "验证码错误") 

4. 获取用户信息接口

4.1 前端源码分析

需求说明:当登录成功以后,那么此时会调用后端接口获取登录成功以后的用户信息,然后在首页面展示

前置路由守卫:在当前的系统中提供了前置路由守卫,在该前置路由守卫中会调用后端服务器端口获取用户信息。

4.1.1 请求发送分析

前置路由守卫的配置在permission.js,该文件以及被main.js引入。因此查看源码以当前js为入口进行分析:

permission.js

// vue-router4的路由守卫不再是通过next放行,而是通过return返回true或false或者一个路由地址
router.beforeEach(async to => {

  if (!window.localStorage[TOKEN]) {  // 如果token不存在,此时跳转到登录页面
    return {
      name: 'login',
      query: {
        redirect: to.fullPath, // redirect是指登录之后可以跳回到redirect指定的页面
      },
      replace: true,
    }
  } else {		// token存在
    const { userinfo, getUserinfo } = useAccount()		// 从pinia的用于账户模块解析出userinfo,getUserInfo方法
    // 获取用户角色信息,根据角色判断权限
    if (!userinfo) {
      try {
        // 获取用户信息
        await getUserinfo()		// 调用getUserInfo方法获取用户数据
      } catch (err) {
        loadingInstance.close()
        return false
      }
      return to.fullPath
    }
  }
})

pinia/modules/account.js源码分析

import { GetUserinfo } from '@/api/login'
export const useAccount = defineStore('account', {
  state: () => ({
    userinfo: null,     // pinia账户模块存储的用户信息
    permissionList: [],
  }),
  actions: {
    // 清除用户信息
    clearUserinfo() {
      this.userinfo = null
    },
    // 获取用户信息
    async getUserinfo() {
      const { code, data } = await GetUserinfo()  // 调用/api/login.js中的GetUserinfo方法,请求后端接口
      if (+code === 200) {
        this.userinfo = data
        return Promise.resolve(data)
      }
    },
  },
})

api/login.js源码分析:

// 获取登录用户信息
export const GetUserinfo = () => {
  return request({
    url: '/api/userinfo',		// 请求后端的接口地址,后期需要将其更改为
    method: 'get',
  })
}

3.1.2 用户信息使用

获取到当前登录成功以后的用户信息,将用户信息存储到Pinia的account模块中以后,该用户信息会在首页的进行使用。首页布局分析,以及对应的组

件说明:

在这里插入图片描述

涉及到的核心组件关系说明:

在这里插入图片描述

layout/components/Topbar/Userinfo.vue组件源码分析:

<template>
  <el-dropdown trigger="hover">
    <div class="userinfo">
      <template v-else>
        <img class="avatar" :src="userinfo.avatar" />  <!-- 从user对象中获取avatar属性值 -->
        {{ userinfo.name }}  <!-- 从user对象中获取name属性值 -->
      </template>
    </div>
  </el-dropdown>
</template>
<script>
import { useUserinfo } from '@/components/Avatar/hooks/useUserinfo'  // 导入该目录下useUserinfo.文件
export default defineComponent({
  setup() {
    const { userinfo } = useUserinfo()  // 调用导入的js文件中的useUserinfo方法,从Pinia中获取用户数据 
    return {
      userinfo,
    }
  },
})
</script>

通过源码查询得出结论:后端返回的数据中需要至少包含两个属性:avatar【用户头像的url】、name【用户名】

3.1.3 token传递

当登录成功以后,后端会给前端返回token数据。前端会将token数据存储到Pinia的app模块中。并且会将token数据保存到localStorage中。当再次请

求获取登录用户信息接口的时候,就需要将token传递到后端。

token的传递是通过axios的请求前置拦截器进行完成的,源码如下所示:utils/request.js

// 拦截请求
service.interceptors.request.use(
  config => {
    const { authorization } = useApp()   // 从Pinia的app模块中获取登录成功以后的用户数据
    if (authorization) {
        
      // 添加一个请求头Authorization , 该请求头所对应的值为:Bearer token数据
      config.headers.Authorization = `Bearer ${authorization.token}`
      
      // 上传传递方式后端解析太麻烦,因此可以更改传递token方式为如下方式
      // config.headers.token = `${authorization.token}`
      
    }
    return config
  },
  error => {
    // console.log(error);
    return Promise.reject(error)
  }
)

4.2 后端接口

4.2.1 IndexController

IndexController中添加如下接口方法:

@GetMapping(value = "/getUserInfo")
public Result<SysUser> getUserInfo(@RequestHeader(name = "token") String token) {
    SysUser sysUser = sysUserService.getUserInfo(token) ;
    return Result.build(sysUser , ResultCodeEnum.SUCCESS) ;
}

4.2.2 SysUserService

SysUserService添加根据token获取用户数据接口方法:

// com.atguigu.spzx.manager.service.impl.SysUserServiceImpl
public SysUser getUserInfo(String token) {
    String userJson = stringRedisTemplate.opsForValue().get("user:login:" + token);
    return JSON.parseObject(userJson , SysUser.class) ;
}

4.3 前端接入

更改前端发送请求的接口地址:api/login.js

// 获取登录用户信息
export const GetUserinfo = () => {
  return request({
    url: '/admin/system/index/getUserInfo',
    method: 'get',
  })
}

4.4 进入首页

获取登录用户信息的接口开发完毕以后,此时还是无法进入到首页。因为在前置路由守卫中还存一段代码是获取当前登录用户的菜单信息,源码如下所

示:permission.js

// 生成菜单(如果你的项目有动态菜单,在此处会添加动态路由)
const { menus, generateMenus } = useMenus()
if (menus.length <= 0) {
    try {
        //此方法用来生成菜单,需要进入方法修改为固定菜单
        await generateMenus()
        return to.fullPath // 添加动态路由后,必须加这一句触发重定向,否则会404
    } catch (err) {
        loadingInstance.close()
        return false
    }
}

当前先不做动态菜单的功能,因此需要把pinia/modules/menu.js中generateMenus()获取动态菜单的代码注释掉:

const generateMenus = async () => {
    
    // // 方式一:只有固定菜单
    const menus = getFilterMenus(fixedRoutes)
    setMenus(menus)

    // 方式二:有动态菜单
    // 从后台获取菜单
    // const { code, data } = await GetMenus()

    // if (+code === 200) {
    //   // 添加路由之前先删除所有动态路由
    //   asyncRoutes.forEach(item => {
    //     router.removeRoute(item.name)
    //   })
    //   // 过滤出需要添加的动态路由
    //   const filterRoutes = getFilterRoutes(asyncRoutes, data)
    //   filterRoutes.forEach(route => router.addRoute(route))

    //   // 生成菜单
    //   const menus = getFilterMenus([...fixedRoutes, ...filterRoutes])
    //   setMenus(menus)
    // }
    
}

5. 退出功能

5.1 需求分析

需求:用户在首页点击退出按钮,那么此时请求后端接口完成退出

实现思路:

1、后端根据token从Redis中删除用户数据

2、前端清空Pinia中保存的用户数据、从localStorage中删除用户token

前端删除数据的代码以及实现:layout\Topbar\Userinfo.vue

// 退出
const logout = () => {
    // 清除token
    useApp().clearToken()
    router.push('/login')
}

5.2 代码实现

5.2.1 后端接口

IndexController

在IndexController中添加接口方法

@GetMapping(value = "/logout")
public Result logout(@RequestHeader(value = "token") String token) {
    sysUserService.logout(token) ;
    return Result.build(null , ResultCodeEnum.SUCCESS) ;
}
SysUserService
@Override
public void logout(String token) {
    stringRedisTemplate.delete("user:login:" + token) ;
}

5.2.2 前端接入

login.js

在src\api\login.js文件中添加如下代码:

// 退出功能
export const Logout = () => {
    return request({
        url: '/admin/system/index/logout',
        method: 'get',
    })
}
Userinfo.vue

修改layout\Topbar\Userinfo.vue的退出方法代码:

import { defineComponent , getCurrentInstance} from 'vue'
import { Logout } from '@/api/login'

export default defineComponent({
    setup() {
        const { proxy: ctx } = getCurrentInstance() // 可以把ctx当成vue2中的this
        // 退出
        const logout = async () => {
            const { code ,  data , message } = await Logout() ;
            if(code == 200) {
                // 清除token
                useApp().clearToken()
                router.push('/login')
            }else {
                ctx.$message.error(message)
            }
        }
        return {
            userinfo,
            logout,
        }
    },
})

pi\login.js文件中添加如下代码:

// 退出功能
export const Logout = () => {
    return request({
        url: '/admin/system/index/logout',
        method: 'get',
    })
}
Userinfo.vue

修改layout\Topbar\Userinfo.vue的退出方法代码:

import { defineComponent , getCurrentInstance} from 'vue'
import { Logout } from '@/api/login'

export default defineComponent({
    setup() {
        const { proxy: ctx } = getCurrentInstance() // 可以把ctx当成vue2中的this
        // 退出
        const logout = async () => {
            const { code ,  data , message } = await Logout() ;
            if(code == 200) {
                // 清除token
                useApp().clearToken()
                router.push('/login')
            }else {
                ctx.$message.error(message)
            }
        }
        return {
            userinfo,
            logout,
        }
    },
})

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

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

相关文章

微服务 人工智能AI 物联网智慧工地云平台源码

目录 ​编辑 智慧工地架构 智慧工地系统 智慧工地云平台功能模块 1、基础数据管理 2、考勤管理 3、安全隐患管理 4、视频监控 5、塔吊监控 6、升降机监控 7、移动端数据推送 智慧工地管理平台子系统构成 智慧工地物联网解决方案&#xff0c;对工地施工安全人员、设…

三种食物轮流吃,睡眠时间又长又香!

睡眠质量一直是人们关注的焦点&#xff0c;而饮食则被认为是影响睡眠的重要因素之一。近年来&#xff0c;有一种食物搭配方法备受瞩目&#xff0c;据说可以让人们的睡眠时间又长又香。这种方法并不复杂&#xff0c;只需要轮流食用三种特定食物&#xff0c;就能有效改善睡眠质量…

window server 2012 r2配置多用户远程登录

window server2012r2配置多用户远程登录 注&#xff1a;window系统默认只允许一个用户登录&#xff0c;但在配置中可以设置多用户同时远程。非特殊情况不建议设置多用户远程&#xff0c;以防数据丢失&#xff0c;或数据篡改无法排查。 1、桌面winr打开gpedit.msc 2、点击管理…

BigTime 2024:多人对战新期待,链游迎来强势回归

随着2024年加密货币市场行情回暖&#xff0c;区块链游戏正成为行业中充满活力的领域。截至2023年&#xff0c;该市场的价值已超过30亿美元&#xff0c;并预计到2030年将达到900亿美元。这种增长部分源于投资的增加和NFT的广泛应用。同时&#xff0c;融合传统游戏元素与区块链技…

简单网站模板1(HTML)

想要拥有自己的网站&#xff0c;却不知该如何才能简约好看&#xff0c;接下来分享一种自己搭建的网站模板&#xff0c;希望大家喜欢。 展示图&#xff1a; CODE: <!DOCTYPE html> <html> <head><title>我的网站</title><style>body {fo…

如何开展有效的绩效面谈

绩效面谈作为绩效管理的核心环节&#xff0c;其重要性不容忽视。它不仅是评价员工过去一段时间工作表现的环节&#xff0c;更是为下一阶段绩效管理设定目标和方向的环节。然而&#xff0c;许多企业在实施绩效面谈时&#xff0c;往往仅停留在形式上&#xff0c;没有真正地发挥其…

分享:大数据信用报告查询的价格一般要多少钱?

现在很多人都开始了解自己的大数据信用了&#xff0c;纷纷去查大数据信用报告&#xff0c;由于大数据信用与人行征信有本质的区别&#xff0c;查询方式和价格都不是固定的&#xff0c;本文就为大家详细讲讲大数据信用报告查询的价格一般要多少钱&#xff0c;希望对你有帮助。 大…

windows上elasticsearch的ik分词器的安装

下载 下载地址 在elasticsearch下的plugins文件夹下创建ik的文件夹 下载的ik压缩包解压到plugins/ik 重启elasticsearch 验证 http://ip:9200/_cat/plugins

2024可持续发展与电力系统、能源国际会议(ICSDPSE 2024)

2024可持续发展与电力系统、能源国际会议&#xff08;ICSDPSE 2024&#xff09; 一、【会议简介】 非常高兴邀请您参加2024年可持续发展与电力系统、能源国际会议&#xff08;ICSDPSE 2024&#xff09;。该会议将于2024年在苏州举行&#xff0c;这是一个旨在促进可持续发展和…

log4j 基础使用入门教程

一、Log4j介绍 在项目中&#xff0c;不管是开发人员写代码还是测试人员写的测试代码一般都需要做一些日志来记录项目的行为&#xff0c;以便更好的跟踪项目中的一些交互和问题。 Log4j ( Logger For Java ) , Java 日志的记录包。 官方网站 。Log4j 是 Apache 的一个开源项目…

虚拟机内存不够用了?全流程操作Look一下?

虚拟机信息&#xff1a;操作系统&#xff1a;CentOS Linux 7 (Core)&#xff0c;用的是VMware Workstation 16 Pro 版本16.2.3 build-19376536&#xff1b;我的主机 Windows 10 Education, 64-bit (Build 22000.1817) 10.0.22000 前言&#xff1a;虚拟机用久了就会出现内存不足…

JVM之CMS垃圾收集器详解

CMS垃圾收集器 CMS回收流程 官网&#xff1a; https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/cms.html#concurrent_mark_sweep_cms_collector CMS(Concurrent Mark Sweep)收集器是一种以获取 最短回收停顿时间为目标的收集器。 采用的是"标记-清除…

【精选】Java项目介绍和界面搭建——拼图小游戏 上

&#x1f36c; 博主介绍&#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 hacker-routing &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【应急响应】 【Java】 【VulnHub靶场复现】【面试分析】 &#x1f389;点赞➕评论➕收藏 …

【三维重建】【slam】【分块重建】LocalRF:逐步优化的局部辐射场的鲁棒视图合成

项目地址&#xff1a;https://localrf.github.io/ 题目&#xff1a;Progressively Optimized Local Radiance Fields for Robust View Synthesis 来源&#xff1a;KAIST、National Taiwan University、Meta 、University of Maryland, College Park 提示&#xff1a;文章用了s…

java 大学生社团管理系统Myeclipse开发mysql数据库web结构jsp编程计算机网页项目

一、源码特点 java 大学生社团管理系统是一套完善的java web信息管理系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为 TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据库为Mysql5…

Java 1.8 docker 镜像制作

文章目录 一、下载文件二、精简JRE三、Dockerfile四、构建镜像五、容器测试 一、下载文件 glibc 下载地址 glibc-2.33-r0.apk glibc-bin-2.33-r0.apk glibc-i18n-2.33-r0.apk rsa sgerrand.rsa.pub jre 1.8 jre-8u201-linux-x64.tar.gz 二、精简JRE 解压 tar -zxvf jre-8…

【网络那些事】

【云计算】 云计算&#xff1a;把计算资源放在某个地方&#xff0c;并通过互联网暴露出来&#xff0c;让用户可以按需使用计算资源的方式&#xff0c;就是所谓的云计算 云计算的三种服务&#xff1a; 云平台专业名词 日常叫法 亚马逊云叫法 云服务器 ECS &#xff08;Elas…

代码随想录算法刷题训练营day27:LeetCode(39)组合总和、LeetCode(40)组合总和 II、LeetCode(131)分割回文串

代码随想录算法刷题训练营day27&#xff1a;LeetCode(39)组合总和、LeetCode(40)组合总和 II、LeetCode(131)分割回文串 LeetCode(39)组合总和 题目 代码 import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List;clas…

ntp时钟服务安装- 局域网节点时间同步

场景&#xff1a; 一般部署大数据相关应用服务&#xff0c;各个节点之间需要时间同步&#xff1b;内网情况下&#xff0c;很可能各节点之前时间可能不一致&#xff0c;或者过一段时间后 又不一致了 ntp 时钟服务器&#xff1a; 可用于内网各个节点之前得时间同步&#xff0c;安…

动态规划|【路径问题】63.不同路径II

目录 题目 题目解析 动态规划思路 1.状态表示 2.状态转移方程 3.初始化 4.填表顺序 5.返回值 代码 题目 63. 不同路径 II 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。…