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.rhkb.cn/news/269580.html

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

相关文章

【学习心得】响应数据加密的原理与逆向思路

一、什么是响应数据加密&#xff1f; 响应数据加密是常见的反爬手段的一种&#xff0c;它是指服务器返回的不是明文数据&#xff0c;而是加密后的数据。这种密文数据可以被JS解密进而渲染在浏览器中让人们看到。 它的原理和过程图如下&#xff1a; 二、响应数据加密的逆向思路 …

docker中hyperf项目配置虚拟域名

在学习hyperf框架时遇到一些问题&#xff0c;这里是直接用了docker环境 下载镜像运行容器 docker run --name hyperf -v /data/project:/data/project -p 9501:9501 -itd -w /data/project --privileged -u root --entrypoint /bin/sh 镜像ID配置docker-compose.yml version…

[译]BNF 表示法:深入了解 Python 的语法

[译]BNF 表示法&#xff1a;深入了解 Python 的语法 原文&#xff1a;《BNF Notation: Dive Deeper Into Python’s Grammar》 https://realpython.com/python-bnf-notation/ 在阅读Python文档的时候&#xff0c;你可能已经遇到过BNF(Backus–Naur form)表示法&#xff1a; 下…

2024年2月24日~2024年3月1日周报(调整网络结构)

文章目录 一、前言二、实验情况2.1 结果展示2.2 灵感收集 三、小结 一、前言 上周学习了数学表达式、了解了DDNet的网络框架。   在本周&#xff0c;寻找改进网络框架与超参数的灵感&#xff0c;并跑代码查看效果。另外&#xff0c;完成了毕业设计开题报告任务。 二、实验情…

web游戏-飞机大战

H5小游戏源码、JS开发网页小游戏开源源码大合集。无需运行环境,解压后浏览器直接打开。有需要的,私信本人,发演示地址,可以后再订阅,发源码,含60+小游戏源码。如五子棋、象棋、植物大战僵尸、开心消消乐、扑鱼达人、飞机大战等等 <!DOCTYPE html> <html lang=&q…

YOLOv6、YOLOv7、YOLOv8网络结构图(清晰版)

承接上一篇博客&#xff1a;YOLOv3、YOLOv4、YOLOv5、YOLOx的网络结构图(清晰版)_yolox网络结构图-CSDN博客 1. YOLOv6网络结构图 2. YOLOv7网络结构图 3. YOLOv8网络结构图

O2O:Offline–Online Actor–Critic

IEEE TAI 2024 paper 1 Introduction 一篇offline to online 的文章&#xff0c;有效解决迁移过程出现的performance drop。所提出的O2AC算法首先在离线阶段添加一项BC惩罚项&#xff0c;用于限制策略靠近专家策略&#xff1b;而在在线微调阶段&#xff0c;通过动态调整BC的权…

手写分布式配置中心(二)实现分布式配置中心的简单版本

这一篇文章比较简单&#xff0c;就是一个增删改查的服务端和一个获取配置的客户端&#xff0c;旨在搭建一个简单的配置中心架构&#xff0c;代码在 https://gitee.com/summer-cat001/config-center 服务端 服务端选择用springboot 2.7.14搭建&#xff0c;设计了4个接口/confi…

物联网主机:为智能交通赋能

物联网&#xff08;IoT&#xff09;技术的发展为智能交通领域带来了许多创新的解决方案。而在物联网应用中&#xff0c;物联网主机起着关键的作用。本文将为大家介绍一款名为E6000的物联网主机&#xff0c;它是一种多协议、多接口的物联网主机&#xff0c;为智能交通系统的建设…

It is also possible that a host key has just been changed

问题&#xff1a;ssh失败&#xff0c;提示如上图 分析: ssh的key存在上图里的路径里。 解决&#xff1a;win10删这个文件C:\Users\admin\.ssh\known_hosts , linux删这个文件.ssh\known_hosts ,或者删除这个文件里的制定ip的那一行&#xff0c;例如“106.1.1.22 ecdsa-sha2-…

FreeRTOS操作系统学习——FreeRTOS工程创建

FreeROTS工程创建 详细步骤 如无特殊情况&#xff0c;大部人都要配置为外部高速时钟 另外&#xff0c;本实验使用了FreeRTOS&#xff0c;FreeRTOS的时基使用的是Systick&#xff0c;而 STM32CubeMX中默认的HAL库时基也是Systick&#xff0c;为了避免可能的冲突&#xff0c;最…

【学习】torchvision.datasets.ImageFolder()

在分类任务中&#xff0c;数据集文件存储往往是如下形式&#xff1a; - train- class1- image1.jpg- image2.jpg...- class2- image1.jpg- image2.jpg......此时&#xff0c;我们想要获取图片和标签&#xff0c;标签即为文件名&#xff08;class1、class2…&#xff09; 可以使…

数据链路层----滑动窗口协议的相关计算

目录 1.窗口大小的相关计算 •停等协议&#xff1a; •后退N帧协议&#xff1a; •选择重传协议&#xff1a; 2.信道利用率相关计算 •停等协议的信道利用率&#xff1a; •连续ARQ&#xff08;后退N帧协议&#xff0c;选择重传协议&#xff09;的信道利用率&#xff1a;…

Java 反射详解:动态创建实例、调用方法和访问字段

“一般情况下&#xff0c;我们在使用某个类之前已经确定它到底是个什么类了&#xff0c;拿到手就直接可以使用 new 关键字来调用构造方法进行初始化&#xff0c;之后使用这个类的对象来进行操作。” Writer writer new Writer(); writer.setName("少年");像上面这个…

no declaration can be found for element ‘rabbit:connection-factory‘

spring-mvc 配置 rabbitmq 出现问题。 我的解决方案如下&#xff1a; 1 找到配置文件 spring-rabbitmq.xml 我的配置文件叫&#xff1a;spring-rabbitmq.xml&#xff0c;你们按照自己的查找。 2 定位如下URI 接着 Ctrl鼠标左键 3 确定spring-rabbit-x.x.xsd 按照步骤2 &…

鸿蒙Harmony应用开发—ArkTS声明式开发(通用属性:组件标识)

id为组件的唯一标识&#xff0c;在整个应用内唯一。本模块提供组件标识相关接口&#xff0c;可以获取指定id组件的属性&#xff0c;也提供向指定id组件发送事件的功能。 说明&#xff1a; 从API Version 8开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容…

C++基于多设计模式下的同步异步日志系统day7(终)

C基于多设计模式下的同步&异步日志系统day7&#xff08;终&#xff09; &#x1f4df;作者主页&#xff1a;慢热的陕西人 &#x1f334;专栏链接&#xff1a;C基于多设计模式下的同步&异步日志系统 &#x1f4e3;欢迎各位大佬&#x1f44d;点赞&#x1f525;关注&#…

美易官方《盘前:美国股指期货温和走低》

美国股指期货在盘前交易中温和走低&#xff0c;市场情绪在美联储主席鲍威尔即将作证前显得谨慎。投资者对即将公布的证词内容充满期待&#xff0c;以寻求对美联储未来货币政策的更多线索。 鲍威尔即将在国会作证&#xff0c;这是市场关注的焦点事件之一。他的证词可能会对美元汇…

字节同事问我:我的Postman为什么连不了数据库?

postman本身没有数据库连接功能&#xff0c;所以用到了node.js中的xmysql实现Rest API的生成&#xff0c;利用postman进行请求&#xff0c;获取需要的数据&#xff0c;来做数据准备或断言。 1 安装 安装node.js&#xff1a;要求版本大于等于7.6 首先保证你的环境上有node.js…

【NR 定位】3GPP NR Positioning 5G定位标准解读(四)

目录 前言 6 Signalling protocols and interfaces 6.1 支持定位操作的网络接口 6.1.1 通用LCS控制平面架构 6.1.2 NR-Uu接口 6.1.3 LTE-Uu接口 6.1.4 NG-C接口 6.1.5 NL1接口 6.1.6 F1接口 6.1.7 NR PC5接口 6.2 终端协议 6.2.1 LTE定位协议&#xff08;LPP&#x…