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,}},
})
相关文章:
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 后端接口…...
k8s初始化报错 [ERROR CRI]: container runtime is not running: ......
一、环境参数 linux系统为centos7kubernetes版本为v1.28.2containerd版本为1.6.28 二、报错内容 执行初始化命令kubeadm init命令时报错,内容如下 error execution phase preflight: [preflight] Some fatal errors occurred:[ERROR CRI]: container runtime is…...
vscode windows 免密登录 powershell.sh
Linux 生成秘钥 ssh-keygenwindows powershell.sh $HOST_IP"zhang192.168.1.1" $PUBPATH"$HOME\.ssh\id_rsa.pub" $KEY(Get-Content "$PUBPATH" | Out-String); ssh "$HOST_IP" "mkdir -p ~/.ssh && chmod 700 ~/.ssh …...
10 种3D 建模技术
在本文中,我将列出 10 种不同类型的 3D 建模。也许可以了解下一个项目将走向何方,或者你可能会像我一样惊讶,究竟有多少 3D 被用作以多种方式进行可视化的工具。这些是我们将讨论和探索的建模类型: 盒子造型多边形建模Nurbs 和曲…...
常见的socket函数封装和多进程和多线程实现服务器并发
常见的socket函数封装和多进程和多线程实现服务器并发 1.常见的socket函数封装2.多进程和多线程实现服务器的并发2.1多进程服务器2.2多线程服务器2.3运行效果 1.常见的socket函数封装 accept函数或者read函数是阻塞函数,会被信号打断,我们不能让它停止&a…...
Tomcat架构分析
Tomcat的核心组件 Tomcat将请求器和处理器分离,使用多种请求器支持不同的网络协议,而处理器只有一个。从而网络协议和容器解耦。 Tomcat的容器 Host:Tomcat提供多个域名的服务,其将每个域名都视为一个虚拟的主机,在…...
旧项目集成阿里云滑动验证码(web和H5方式)
简述 旧项目集成阿里云滑动验证码(web和H5方式) 适用于servlet和HTML项目,VUE + springboot请看另一篇文档 前情提示 系统: 一说 部分截图、链接等因过期、更换域名、MD语法等可能不显示,可联系反馈(备注好博文地址),谢谢❤带有#号、删除线、不操作、不执行…...
机器人内部传感器阅读梳理及心得-速度传感器-数字式速度传感器
在机器人控制系统中,增量式编码器既可以作为位置传感器测量关节相对位置,又可作为速度传感器测量关节速度。当作为速度传感器时,既可以在模拟量方式下使用,又可以在数字量方式下使用。 模拟式方法 在这种方式下,需要…...
【vue+element ui】大屏自适应中el-select下拉内容在低分辨率下显示不全问题解决
目录 背景 现象 解决方案 背景 最近要把一个1920px*1080px的大屏改成自适应的;最低适配到1028px*720px; 现象 自适应适配改完之后 将电脑屏幕改成1028px*720px分辨率后,下拉显示正常 通过谷歌浏览器设置Toggle device toolbar为1028px*…...
前端架构: 脚手架之多package项目管理和架构
多package项目管理 1 )多package项目管理概述 通常来说,当一个项目变大了以后,我们就要对这个项目进行拆分在前端当中,对于项目进行拆分的方式,通常把它称之为javascript包管理需要使用一个工具叫做 npm (Node Packag…...
【C# 多线程】如何停止正在运行中的子线程
如何停止正在运行中的子线程 通过协作式取消模式强制终止线程(可能存在资源不能及时释放的泄漏风险) 通过协作式取消模式 在线程函数中,你可以周期性地检查一个标志位,以确定是否应该停止线程。该标记位可以是共享变量࿰…...
服务器机房安全守护:五大物理安全实践
服务器机房是数字企业的心脏。无论是企业家还是经验丰富的IT专业人员,都知道服务器机房的安全性至关重要。如果没有采用适当的物理安全措施,其服务器很容易受到盗窃、人为破坏和自然灾害的破坏。 在保护服务器机房的领域内,需要采用多方面的…...
spring boot 修复 Spring Framework URL解析不当漏洞(CVE-2024-22243)
漏洞描述 当应用程序使用UriComponentsBuilder来解析外部提供的URL(如通过查询参数)并对解析的URL的主机执行验证检查时可能容易受到Open重定向攻击和SSRF攻击,导致网络钓鱼和内部网络探测等。 受影响产品或系统 6.1.0 < Spring Framew…...
VR全景HDR拍摄教程
什么是HDR? HDR可以用在哪里? 书面解释:HDR(高动态范围 High Dynamic Range)摄影,是摄影领域广泛使用的一种技术。 是不是有点懵? 我们来看一个实际的拍摄现场环境,你就懂了 我们…...
hive执行select count(1)返回0
背景: 做数据质量检核任务的时候,有些数据表有数据,直接查hive执行select count(1) from table返回的值一直是0 问题原因: hive通过select count(1)或者select count(*) 查询的是元数据库里面的rownum,如果数据表数据是通过load、…...
3D Gaussian splatting 协方差矩阵 球谐函数 简单理解
3D Gaussian splatting 是一种图形和视觉处理技术,常用于体积渲染、点云渲染和其他应用中,以便对数据进行平滑或重建。在这个上下文中,高斯分布(或高斯“splat”)用于表示单个数据点(如一个点云中的点&…...
代码随想录算法刷题训练营day27:LeetCode(39)组合总和、LeetCode(40)组合总和 II、LeetCode(131)分割回文串
代码随想录算法刷题训练营day27: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…...
docker 容器修改端口和目录映射
一、容器修改端口映射 一般在运行容器时,我们都会通过参数 -p(使用大写的-P参数则会随机选择宿主机的一个端口进行映射)来指定宿主机和容器端口的映射,例如 docker run -it -d --name [container-name] -p 8088:80 [image-name]…...
echarts鼠标事件
鼠标事件支持方法 ECharts 支持常规的鼠标事件类型,包括 ‘click’、 ‘dblclick’、 ‘mousedown’、 ‘mousemove’、 ‘mouseup’、 ‘mouseover’、 ‘mouseout’、 ‘globalout’、 ‘contextmenu’ 事件 简单实例 // 基于准备好的dom,初始化EChar…...
【北京迅为】《iTOP-3588开发板网络环境配置手册》第2章 电脑、开发板直连交换机或路由器
RK3588是一款低功耗、高性能的处理器,适用于基于arm的PC和Edge计算设备、个人移动互联网设备等数字多媒体应用,RK3588支持8K视频编解码,内置GPU可以完全兼容OpenGLES 1.1、2.0和3.2。RK3588引入了新一代完全基于硬件的最大4800万像素ISP&…...
超短脉冲激光自聚焦效应
前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应,这是一种非线性光学现象,主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场,对材料产生非线性响应,可能…...
Opencv中的addweighted函数
一.addweighted函数作用 addweighted()是OpenCV库中用于图像处理的函数,主要功能是将两个输入图像(尺寸和类型相同)按照指定的权重进行加权叠加(图像融合),并添加一个标量值&#x…...
CentOS下的分布式内存计算Spark环境部署
一、Spark 核心架构与应用场景 1.1 分布式计算引擎的核心优势 Spark 是基于内存的分布式计算框架,相比 MapReduce 具有以下核心优势: 内存计算:数据可常驻内存,迭代计算性能提升 10-100 倍(文档段落:3-79…...
(二)原型模式
原型的功能是将一个已经存在的对象作为源目标,其余对象都是通过这个源目标创建。发挥复制的作用就是原型模式的核心思想。 一、源型模式的定义 原型模式是指第二次创建对象可以通过复制已经存在的原型对象来实现,忽略对象创建过程中的其它细节。 📌 核心特点: 避免重复初…...
Java多线程实现之Callable接口深度解析
Java多线程实现之Callable接口深度解析 一、Callable接口概述1.1 接口定义1.2 与Runnable接口的对比1.3 Future接口与FutureTask类 二、Callable接口的基本使用方法2.1 传统方式实现Callable接口2.2 使用Lambda表达式简化Callable实现2.3 使用FutureTask类执行Callable任务 三、…...
vue3 定时器-定义全局方法 vue+ts
1.创建ts文件 路径:src/utils/timer.ts 完整代码: import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...
实现弹窗随键盘上移居中
实现弹窗随键盘上移的核心思路 在Android中,可以通过监听键盘的显示和隐藏事件,动态调整弹窗的位置。关键点在于获取键盘高度,并计算剩余屏幕空间以重新定位弹窗。 // 在Activity或Fragment中设置键盘监听 val rootView findViewById<V…...
使用 SymPy 进行向量和矩阵的高级操作
在科学计算和工程领域,向量和矩阵操作是解决问题的核心技能之一。Python 的 SymPy 库提供了强大的符号计算功能,能够高效地处理向量和矩阵的各种操作。本文将深入探讨如何使用 SymPy 进行向量和矩阵的创建、合并以及维度拓展等操作,并通过具体…...
Mobile ALOHA全身模仿学习
一、题目 Mobile ALOHA:通过低成本全身远程操作学习双手移动操作 传统模仿学习(Imitation Learning)缺点:聚焦与桌面操作,缺乏通用任务所需的移动性和灵活性 本论文优点:(1)在ALOHA…...
Webpack性能优化:构建速度与体积优化策略
一、构建速度优化 1、升级Webpack和Node.js 优化效果:Webpack 4比Webpack 3构建时间降低60%-98%。原因: V8引擎优化(for of替代forEach、Map/Set替代Object)。默认使用更快的md4哈希算法。AST直接从Loa…...
