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 {@Overridepublic 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.ExceptionUtilslog.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.ExceptionUtilslog.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 ;        // 验证码的keyprivate 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 {@Autowiredprivate StringRedisTemplate<String , String> stringRedisTemplate ;@Overridepublic ValidateCodeVo generateValidateCode() {// 使用hutool工具包中的工具类生成图片验证码CircleCaptcha circleCaptcha = CaptchaUtil.createCircleCaptcha(150, 48, 4, 20);String codeValue = circleCaptcha.getCode();String imageBase64 = circleCaptcha.getImageBase64();// 生成uuid作为图片验证码的keyString 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-inputclass="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.codeKeystate.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 = datareturn 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 = () => {// 清除tokenuseApp().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) {// 清除tokenuseApp().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) {// 清除tokenuseApp().clearToken()router.push('/login')}else {ctx.$message.error(message)}}return {userinfo,logout,}},
})

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java找工作之JavaWeb(一)

JavaWeb 一个web应用有多部份组成&#xff08;静态web&#xff0c;动态web&#xff09; html&#xff0c;css&#xff0c;jsjsp&#xff0c;servletjava程序jar包配置文件(Properties) web应用程序编写完毕后&#xff0c;若想提供给外界访问&#xff0c;需要一个服务器来统一…

【IC验证】数据类型

一、思维导图 二、代码 &#xff08;1&#xff09;字符串 字符串的结尾不带空标识符null&#xff0c;\0。是动态存储方式&#xff0c;因此不用担心存储空间被用完。 编辑.SV文件使用 gvim 文件名 运行仿真使用 vcs -sverilog -full64 string_type.sv 和./simv命令如果是字…

【微服务】微服务架构包含哪些组件、各个组件又承担什么职责呢?

前面的文章里我们介绍了微服务架构的一些基础知识以及特点&#xff0c;也提到了国产的微服务架构标准实现SpringCloud Alibaba&#xff0c;聊了这么多&#xff0c;还是没有开始阐述各组件在Java技术体系中的具体实现。那从这里开始&#xff0c;我们开始全面认识微服务架构在Jav…

上拉电阻与下拉电阻、电容的作用

上拉电阻与下拉电阻 在单片机电路中&#xff0c;上拉电阻和下拉电阻都是常见的电路元件&#xff0c;它们在数字电路设计中扮演着重要的角色。它们的作用如下&#xff1a; 1. **上拉电阻**&#xff1a; - **作用**&#xff1a;当一个引脚没有外部信号时&#xff0c;上拉电阻…

微调(Fine-tuning)技术概念

——微调&#xff0c;像化妆&#xff0c;一种“精细化、风格性调整”的人工美颜技能。 微调&#xff08;Fine-tuning&#xff09;是指在深度学习领域中&#xff0c;特别是针对预训练模型的一种训练策略。预先训练好的模型通常是在大规模无标注数据上通过自监督学习得到的&#…

【Micropython】I2C层次结构、I2C协议

文章目录 前言一、I2C的结构层次1.1 怎样在两个设备之间传输数据1.2 I2C如何传输数据1.3 硬件框图1.4 软件层次 二、IIC协议2.1 硬件连接2.2 I2C 总线的概念2.3 传输数据类比2.3 I2C信号2.4 I2C数据的含义 总结 前言 I2C&#xff08;Inter-Integrated Circuit&#xff09;是一…

【Redis】redis事务和发布订阅

Redis 事务 Redis 事务可以一次执行多个命令&#xff0c; 并且带有以下三个重要的特性&#xff1a; 批量操作在发送 EXEC 命令前被放入队列缓存。收到 EXEC 命令后进入事务执行&#xff0c;事务中任意命令执行失败&#xff0c;其余的命令依然被执行。在事务执行过程&#xff…

C++设计模式——抽象工厂模式

文章目录 抽象工厂模式的主要组成部分抽象工厂模式的一个典型例子抽象工厂模式用于其他场景抽象工厂模式与其他设计模式结合使用 C 中的抽象工厂模式是一种创建型设计模式&#xff0c;它主要用于处理对象家族的创建&#xff0c;这些对象之间可能存在一定的关联关系或属于相同的…

国产航顺HK32F030M: HK32F030MJ4M6_SOP8资料

最小系统 参考资料 [1] 航顺MCU HK32F030MJ4M6-SOP8 各个文件夹简介&#xff1a; Boards&#xff1a;HK32F030xMF4P6开发板的BSP驱动代码。 Documents&#xff1a;HK32F030xMxx数据手册、用户手册、API手册以及HK32F030xMxx开发板原理图。 Package&#xff1a;HK32F030xMxx Ke…

任务系统之API子任务

日常运维工作中有许多的任务要执行&#xff0c;例如项目发布/数据备份/定时巡检/证书更新/漏洞修复等等&#xff0c;大部分的任务都会有多个步骤共同完成&#xff0c;例如一个发布任务会有拉代码、编译、分发、通知等等步骤&#xff0c;而不同的任务可能还包含相同或相似的步骤…

PRL算法调控

伴随汽车电子技术发展&#xff0c;传统轮式车辆制动系统的气体或液体传输管路长&#xff0c;阀类原件多原有的真空助力系统无法兼顾车辆的再生制动功能&#xff0c;而再生制动功能是混合动力车辆是混动车辆最主要的市场优势之一&#xff0c;真空助力器逐渐被eBooster 所取代。针…

微信小程序 - 渲染和逻辑

通信模型 渲染层的界面使用了WebView 进行渲染&#xff1b;逻辑层采用JsCore线程运行JS脚本。 数据驱动 WXML会解析对应model值&#xff0c;并生成js对象&#xff0c;最后生成最终的dom树。 当model发生变更时候&#xff0c;会判断解析wxml后的js对象是否改动&#xff0c;若改动…

[重磅更新] Mac玩游戏必备!Crossover24版现已上线!附免费升级攻略 Crossover软件使用方法安装程序 免费版

好久不见啦&#xff0c;最近一直在忙着研究Mac玩游戏&#xff0c;什么幻兽帕鲁、女神异闻录之类的&#xff0c;有些沉迷了&#xff0c;实在对不住大家… 不过今天还是给大家带来了好消息&#xff01;那就是让Mac玩游戏不再是笑话的神器&#xff0c;Crossover正式发布了2024版&a…

使用CANoe进行27服务安全认证解密算法

方法&#xff1a;通过cdd文件dll文件进行27服务通过安全认证解密 步骤1&#xff1a;菜单栏选中Diagnostics&XCP这一栏&#xff0c; 步骤2&#xff1a;鼠标左击CANdelaStudio 步骤3&#xff1a;弹出如下弹窗&#xff0c;选择 I accept 步骤4&#xff1a;选择新建 步骤5&…

redis-Redis主从,哨兵和集群模式

一&#xff0c;Redis的主从复制 ​ 主机数据更新后根据配置和策略&#xff0c; 自动同步到备机的master/slaver机制&#xff0c;Master以写为主&#xff0c;Slave以读为主。这样做的好处是读写分离&#xff0c;性能扩展&#xff0c;容灾快速恢复。 1.1 环境搭建 如果你的redi…

ModStartCMS v8.1.0 图片前端压缩,抖音授权登录

ModStart 是一个基于 Laravel 模块化极速开发框架。模块市场拥有丰富的功能应用&#xff0c;支持后台一键快速安装&#xff0c;让开发者能快的实现业务功能开发。 系统完全开源&#xff0c;基于 Apache 2.0 开源协议&#xff0c;免费且不限制商业使用。 功能特性 丰富的模块市…

手机AI摄影时代开启,传音引领行业标准化建设

今年春节&#xff0c;AI摄影可谓大出风头。人们在社交平台晒出自己在龙年的AI写真&#xff0c;极大地增添了节日的氛围感&#xff0c;也让我们看到了“AI摄影”的价值。新年伊始&#xff0c;手机巨头们纷纷布局该赛道&#xff0c;基于AI大模型实现的影像功能成为业界关注焦点。…

Java Web(十)--jQuery

介绍 官网文档&#xff1a;jQuery 教程 jQuery API 中文文档 | jQuery API 中文在线手册 | jquery api 下载 | jquery api chm 下载地址&#xff1a;https://jquery.com/download/%20jQuery jQuery 是一个快速的&#xff0c;简洁的 javaScrip工具库&#xff0c;使用户能更方…

C语言 变量

变量其实只不过是程序可操作的存储区的名称。C 中每个变量都有特定的类型&#xff0c;类型决定了变量存储的大小和布局&#xff0c;该范围内的值都可以存储在内存中&#xff0c;运算符可应用于变量上。 变量的名称可以由字母、数字和下划线字符组成。它必须以字母或下划线开头…

全网最新的软件测试面试八股文

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 关注公众号【互联网杂货铺】&#xff0c;回复 1 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 测试技术面试题 1、什么是兼容性测试&#xff1f;兼容性测试侧…