当前位置: 首页 > news >正文

模拟实现短信登录功能 (session 和 Redis 两种代码实例) 带前端演示

                                               

目录

整体流程

发送验证码

短信验证码登录、注册

校验登录状态

基于 session 实现登录

实现发送短信验证码功能

1. 前端发送请求

2. 后端处理请求

3. 演示

实现登录功能

1. 前端发送请求

2. 后端处理请求

校验登录状态

1. 登录拦截器

2. 注册拦截器

3. 登录完整演示

session共享问题

问题描述

基于 Redis 实现登录

设计 key 的结构

整体访问流程

基于 session 登录-流程图

基于 Redis 实现共享 session 登录-流程图

代码实现

发短信

登录功能

拦截器功能修改

登录演示

解决状态登录刷新问题

优化方案

代码修改

第一个拦截器

第二个拦截器

涉及到 order 的源码​​​​​​

整体流程

基础模版如下,可以根据具体需求进行修改和拓展!!!!

发送验证码

  • 用户在提交手机号后,会校验手机号是否合法,如果不合法,则要求用户重新输入手机号
  • 如果手机号合法,后台此时生成对应的验证码,同时将验证码进行保存,然后再通过短信的方式将验证码发送给用户

短信验证码登录、注册

用户将验证码和手机号进行输入,后台从 session 中拿到当前验证码,然后和用户输入的验证码进行校验:

如果不一致,则无法通过校验

如果一致,则后台根据手机号查询用户,如果用户不存在,则为用户创建账号信息,保存到数据库,无论是否存在,都会将用户信息保存 session 中,方便后续获得当前登录信息

校验登录状态

用户在请求时候,会从 cookie 中携带者 sessionId 到后台,后台通过 sessionId 从 session 中拿到用户信息,如果没有 session 信息,则进行拦截,如果有 session 信息,则将用户信息保存到 ThreadLocal 中,并且放行。

基于 session 实现登录

实现发送短信验证码功能

1. 前端发送请求

前端输入电话,点击发送验证码,发送请求

2. 后端处理请求

Controller

    @PostMapping("code")public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {// 发送短信验证码并保存验证码return userService.sendCode(phone, session);}

Service

Result sendCode(String phone, HttpSession session);

ServiceImpl

 /*** 发送验证码* @param phone* @param session* @return*/@Overridepublic Result sendCode(String phone, HttpSession session) {// 1、校验手机号if (RegexUtils.isPhoneInvalid(phone)) {// 2. 如果不符合 返回错误信息return  Result.fail("手机号格式错误!");}// 3. 符合生成验证码// hutool 工具类 随机生成一个6位数的号码String code = RandomUtil.randomNumbers(6);// 4. 保存验证码到sessionsession.setAttribute("code",code);// 5. 发送验证码log.debug("模拟发送短信验证码成功,验证码:{}",code);// 结束return Result.ok();}

3. 演示

后端生成验证码

实现登录功能

1. 前端发送请求

从后端获取到验证码

2. 后端处理请求

操作数据库是用的技术是 Mybatis-Plus

Controller

    @PostMapping("/login")public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){// 实现登录功能return userService.login(loginForm, session);}

Service

Result login(LoginFormDTO loginForm, HttpSession session);

ServiceImpl

/*** 登录* @param loginForm* @param session* @return*/@Overridepublic Result login(LoginFormDTO loginForm, HttpSession session) {// 1.检验手机号String phone = loginForm.getPhone();if (RegexUtils.isPhoneInvalid(phone)) {// 2. 如果不符合 返回错误信息return Result.fail("手机号格式错误!");}// 3. 校验验证码// 从session中获取刚刚生成的验证码Object cacheCode = session.getAttribute("code");// 获取从前端发送过来的验证码String code = loginForm.getCode();if (cacheCode == null || !cacheCode.toString().equals(code)) {// 验证码和手机不一致return Result.fail("验证码错误!");}// 4. 验证码和手机一致,根据手机号查询用户 // 使用了 Mybatis-Plus User user = query().eq("phone", phone).one();if (user == null) {// 5. 不存在,创建新用户并保存user = createUserWithPhone(phone);}// 7. 保存用户信息到 session 中UserDTO userDTO = new UserDTO();// 8. 用户信息脱敏BeanUtils.copyProperties(user, userDTO);session.setAttribute("user", userDTO);log.debug("保存用户信息到session user = {}", user);return Result.ok();}

不熟悉 MybasitPlus 的小伙伴可以看看这个文章,快速入个小门

快速熟悉MybatisPlus Mybatis的使用与区别_mybatis和mybatis-plus-CSDN博客

UserDTO

@Data
public class UserDTO {private Long id;private String nickName;private String icon;
}

createUserWithPhone 方法 -- 新增用户

private User createUserWithPhone(String phone) {// 1. 创建一个新用户User user = new User();user.setPhone(phone);user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));// 2. 保存用户save(user);log.debug("创建新用户成功,用户信息:{}",user);return user;
}

注:

根据前端需要渲染的页面,建立相关的 DTO 类做用户信息脱敏,返回一些主要的信息给前端就行。

  1. 减少 session 存储用户所占用的内存大小,从而减小服务器的压力(session 是存在服务器端的嘛)。
  2. 数据量小得话,在网络传输信息的时候花的时间也会更少,加快服务端的响应,降低网络对服务的影响,使用户体验更加。

校验登录状态

温馨小贴士:tomcat的运行原理

当用户发起请求时,会访问我们像tomcat注册的端口,任何程序想要运行,都需要有一个线程对当前端口号进行监听,tomcat 也不例外,当监听线程知道用户想要和 tomcat 连接连接时,那会由监听线程创建 socket 连接socket都是成对出现的,用户通过socket像互相传递数据,当tomcat端的socket接受到数据后此时监听线程会从tomcat的线程池中取出一个线程执行用户请求,在我们的服务部署到tomcat后,线程会找到用户想要访问的工程,然后用这个线程转发到工程中的controller,service,dao 中,并且访问对应的 DB,在用户执行完请求后,再统一返回,再找到 tomcat 端的 socket ,再将数据写回到用户端的 socket ,完成请求和响应

通过上述,我们可以得知,每个用户其实对应都是去找 tomcat 线程池中的一个线程来完成工作的, 使用完成后再进行回收,既然每个请求都是独立的,所以在每个用户去访问我们的工程时,我们可以使用。ThreadLocal 来做到线程隔离每个线程操作自己的一份数据。

因为涉及到线程池,给大家推荐一篇池化技术的文章,可以简单了解了解池化技术,拓宽自己的知识哈!!!

概述池化技术-CSDN博客

1. 登录拦截器


import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.xml.ws.handler.Handler;/*** @author lhd* date 2024/7/26* @apiNote*/
public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1. 获取sessionHttpSession session = request.getSession();// 2. 从 session 中获取用户的状态信息// 用户信息脱敏UserDTO user = (UserDTO) session.getAttribute("user");// 3. 判断用户是否存在if (user == null){// 不存在就拦截response.setStatus(401);return false;}// 5. 存在 保存用户信息到 ThredLocalUserHolder.saveUser(user);// 6. 放行return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {HandlerInterceptor.super.afterCompletion(request, response, handler, ex);}
}

UserHolder

public class UserHolder {private static final ThreadLocal<UserDTO> tl = new ThreadLocal<>();public static void saveUser(UserDTO user){tl.set(user);}public static UserDTO getUser(){return tl.get();}public static void removeUser(){tl.remove();}
}

2. 注册拦截器

/*** @author lhd* date 2024/7/26* @apiNote*/
@Configuration
public class MvcConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 登录拦截器registry.addInterceptor(new LoginInterceptor())// 不拦截的路径.excludePathPatterns("/user/code","/user/login","/blog/hot","/shop/**","/shop-type/**","/voucher/**");WebMvcConfigurer.super.addInterceptors(registry);}
}

3. 登录完整演示

登录跳转到个人中心页面

因为这是一个新用户,后台数据库也会插入这个用户的信息

session共享问题

问题描述

每个 tomcat 中都有一份属于自己的 session ,假设用户第一次访问第一台 tomcat,并且把自己的信息存放到第一台服务器的 session 中,但是第二次这个用户访问到了第二台 tomcat,那么在第二台服务器上,肯定没有第一台服务器存放的 session,所以此时整个登录拦截功能就会出现问题

我们能如何解决这个问题呢?

早期的方案是 session拷贝,就是说虽然每个 tomcat 上都有不同的 session,但是每当任意一台服务器的 session 修改时,都会同步给其他的 tomcat 服务器的 session,这样的话,就可以实现session的共享了。

但是这种方案具有两个大问题:

1、每台服务器中都有完整的一份session数据,服务器压力过大。

2、session拷贝数据时,可能会出现延迟

模拟项目架构图

综上所述,我们可以选用更好的方案,使用 redis,所以咱们后来采用的方案都是基于 redis 来完成,我们把 session 换成 redis,redis 数据本身就是共享的,就可以避免 session共享的问题了

基于 Redis 实现登录

设计 key 的结构

首先我们要思考一下利用 redis 来存储数据,那么到底使用哪种结构呢?

由于存入的数据比较简单,我们可以考虑使用 String ,或者是使用哈希,如下图,如果使用 String,注意他的value,用多占用一点空间,如果使用哈希,则他的 value 中只会存储他数据本身,如果不是特别在意内存,其实使用 String 就可以啦。

所以我们可以使用 String 结构,就是一个简单的 key,value 键值对的方式,但是关于 key 的处理,session 他是每个用户都有自己的 session,但是 redis 的 key 是共享的,咱们就不能使用 code(验证码) 了。

在设计这个 key 的时候,我们之前讲过需要满足两点

        1、key要具有唯一性

        2、key要方便携带

如果我们采用 phone:手机号 作为key,用这个 key 来存储当然是可以的,但是如果把这样的敏感数据存储到 redis 中并且从页面中带过来毕竟不太合适。

所以我们在后台生成一个随机串token,然后让前端带来这个 token 就能完成我们的整体逻辑了。

整体访问流程

基于 session 登录-流程图

基于 Redis 实现共享 session 登录-流程图

总的来说总体功能没变,只是功能实现发生了改变。

主要变化:

  1. 使用 redis 完成用户登录后,需要返回一个随机 token 给前端,以便于用户后续访问。
  2. 发送请求从携带 cookie 转变为携带后端生成的随机 token。
  3. 获取用户信息从 session 中获取转变为通过 token从 redis 中获取。
  4. 获取验证码从 session 中获取转变为以手机号为 key 从 redis 中获取。

代码实现

发短信

    @Resourceprivate StringRedisTemplate stringRedisTemplate;/*** 发送验证码* @param phone* @param session* @return*/@Overridepublic Result sendCode(String phone, HttpSession session) {// 1、校验手机号if (RegexUtils.isPhoneInvalid(phone)) {// 2. 如果不符合 返回错误信息return  Result.fail("手机号格式错误!");}// 3. 符合生成验证码// hutool 工具类 随机生成一个6位数的号码String code = RandomUtil.randomNumbers(6);// 4. 保存验证码到session// session.setAttribute("code",code);// 4.以 String 数据结构 保存验证码到 redis中stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone, code, LOGIN_CODE_TTL, TimeUnit.MINUTES);// 5. 发送验证码log.debug("模拟发送短信验证码成功,验证码:{}",code);// 结束return Result.ok();}

登录功能

/*** 登录* @param loginForm* @param session* @return*/@Overridepublic Result login(LoginFormDTO loginForm, HttpSession session) {// 1.检验手机号String phone = loginForm.getPhone();if (RegexUtils.isPhoneInvalid(phone)) {// 2. 如果不符合 返回错误信息return Result.fail("手机号格式错误!");}// 3. 从redis获取验证码并校验String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);String code = loginForm.getCode();if (cacheCode == null || !cacheCode.equals(code)) {// 验证码不一致return Result.fail("验证码错误!");}// 4.一致,根据手机号查询用户User user = query().eq("phone", phone).one();if (user == null) {// 5. 不存在,创建新用户并保存user = createUserWithPhone(phone);}// 7.保存用户信息到 redis 中// 7.1 随机生成 token 作为登录令牌String token = UUID.randomUUID().toString(true);// 7.2.将User对象转为HashMap存储// BeanUtil 是 hutool 工具类中的 复制 user 的信息 到 脱敏 UserDTO 中UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
//        Map<String, Object> userMap = BeanUtil.beanToMap(userDTO);Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),CopyOptions.create()// 忽略空值错误.setIgnoreNullValue(true)// 自定义转换器 因为 userDTO 中的 id 为 long 类型.setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));// 7.3 存储String tokenKey = LOGIN_USER_KEY + token;// 以 Hash 数据结构存储用户信息// login:token: + token 生成存储字符串作为 key 保存当前用户的信息到 redis中stringRedisTemplate.opsForHash().putAll(tokenKey + token, userMap);// 7.4 设置 token 有效期stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES);log.debug("保存用户信息到 redis user = {}", userDTO);// 8. 返回 tokenreturn Reslt.ok(token);}

拦截器功能修改

LoginInterceptor

@Slf4j
public class LoginInterceptor implements HandlerInterceptor {private  StringRedisTemplate stringRedisTemplate;// 因为这个类不属于 spring 管理的类所以需要通过构造器去获取 StringRedisTemplate类// 构造器注入的方式 挺不错的思想public LoginInterceptor(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1. 获取请求头中的 tokenlog.debug("拦截器被调用");String token = request.getHeader("authorization");System.out.println("从头获取到的token === " + token);if (StrUtil.isBlank(token)){// token为空说明没有登录过return false;}// 2.基于TOKEN获取redis中的用户String key = LOGIN_USER_KEY + token;Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);// 3. 判断用户是否存在if (userMap.isEmpty()){// 不存在就拦截response.setStatus(401);return false;}// 5. 将查询到的 Hash 数据转为 UserDTO对象// 不忽略转换的错误UserDTO user = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);// 6. 保存用户信息到 ThreadLocalUserHolder.saveUser(user);// 7. 刷新 token 有效期stringRedisTemplate.expire(key, RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);log.debug("ThreadLocal 中的User为:" + user);// 8. 放行return true;}

MvcConfig

@Configuration
public class MvcConfig implements WebMvcConfigurer {// 本类加了 @Configuration 项目启动时候会进行自动装配 可以获取到 StringRedisTemplate@Resourceprivate StringRedisTemplate stringRedisTemplate;@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 登录拦截器registry.addInterceptor(new LoginInterceptor(stringRedisTemplate))// 不拦截的路径.excludePathPatterns("/user/code","/user/login","/upload/**","/blog/hot","/shop/**","/shop-type/**","/voucher/**");}
}

抛了个类型转换异常

原因:

因为 StringRedisTemplate key 和 value 都需要 String 类型

解决方案:

一开始 UserDTO 直接转 map,出了一个类型转换异常,需要自定义一下转换规则,login 实现类中将 UserDTO 转换为 map 存入 redis 之前做个调整,将所有 value 都转为 String 类型,在存入map;

        // Map<String, Object> userMap = BeanUtil.beanToMap(userDTO);Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),CopyOptions.create()// 忽略空值错误.setIgnoreNullValue(true)// 自定义转换器 因为 userDTO中的 id 为 long 类型.setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));

登录演示

redis中的数据

验证码

token

数据库数据:

前端页面 --- 登录成功 进入个人主页

解决状态登录刷新问题

在这个方案中,他确实可以使用对应路径的拦截,同时刷新登录token令牌的存活时间,但是现在这个拦截器他只是拦截需要被拦截的路径,假设当前用户访问了一些不需要拦截的路径,那么这个拦截器就不会生效,所以此时令牌刷新的动作实际上就不会执行,所以这个方案他是存在问题的。

优化方案

既然之前的拦截器无法对不需要拦截的路径生效,那么我们可以添加一个拦截器,

在第一个拦截器中拦截所有的路径,把第二个拦截器做的事情放入到第一个拦截器中,同时刷新令牌,因为第一个拦截器有了 ThreadLocal 的数据,所以此时第二个拦截器只需要判断拦截器中的user对象是否存在即可,完成整体刷新功能。

总的来说:

  • 第一个拦截器负责刷新 token 。
  • 第二个拦截器负责拦截需要登录的页面,校验用户当前是否登录。

代码修改

第一个拦截器 刷新token


/*** @author lhd* date 2024/7/26* @apiNote*/
@Slf4j
public class RefreshTokenInterceptor implements HandlerInterceptor {private  StringRedisTemplate stringRedisTemplate;public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1. 获取请求头中的 tokenlog.debug("拦截器被调用");String token = request.getHeader("authorization");System.out.println("从头获取到的token === " + token);if (StrUtil.isBlank(token)){return true;}// 2.基于TOKEN获取redis中的用户String key = LOGIN_USER_KEY + token;Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);// 3. 判断用户是否存在if (userMap.isEmpty()){return true;}// 5. 将查询到的 Hash 数据转为 UserDTO对象// 不忽略转换的错误UserDTO user = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);// 6. 保存用户信息到 ThreadLocalUserHolder.saveUser(user);// 7. 刷新 token 有效期stringRedisTemplate.expire(key, RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);log.debug("ThreadLocal 中的User为:" + user);// 8. 放行return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {// 移除用户UserHolder.removeUser();}
}

第二个拦截器 拦截需要登录的

/*** @author lhd* date 2024/7/26* @apiNote*/
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1. 判断是否需要拦截(ThreadLocal中是否有用户)if (UserHolder.getUser() == null){// 没有信息需要拦截,设置状态码response.setStatus(401);// 拦截return false;}// 有用户信息 则放行return true;}}

MVCconfig

设置 order 是为了让第一个拦截器先执行(0的优先级最大)。

/*** @author lhd* date 2024/7/26* @apiNote*/
@Configuration
public class MvcConfig implements WebMvcConfigurer {@Resourceprivate StringRedisTemplate stringRedisTemplate;@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 登录拦截器registry.addInterceptor(new LoginInterceptor())// 不拦截的路径.excludePathPatterns("/user/code","/user/login","/upload/**","/blog/hot","/shop/**","/shop-type/**","/voucher/**").order(1);registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**").order(0);}
}

注:

拦截器都不设置 order 的参数,就是默认为0, 默认就按照添加顺序实现拦截。

如果要实现第一个拦截器先执行,就把他放在前面。

代码如下:

// token刷新的拦截器registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**");// 登录拦截器registry.addInterceptor(new LoginInterceptor()).excludePathPatterns("/shop/**","/voucher/**","/shop-type/**","/upload/**","/blog/hot","/user/code","/user/login");

如果更加严谨的话就设置 order(0)

涉及到 order 的源码

进入

进入

找到order

 后期会更新使用阿里云发送短信!!!!

相关文章:

模拟实现短信登录功能 (session 和 Redis 两种代码实例) 带前端演示

目录 整体流程 发送验证码 短信验证码登录、注册 校验登录状态 基于 session 实现登录 实现发送短信验证码功能 1. 前端发送请求 2. 后端处理请求 3. 演示 实现登录功能 1. 前端发送请求 2. 后端处理请求 校验登录状态 1. 登录拦截器 2. 注册拦截器 3. 登录完整…...

C# Parallel设置最大并发度

背景 以前用Parallel都是直接用&#xff0c;今天在处理pdf时发现不是很快&#xff0c;特别是有时居然卡死了&#xff0c;异常是有处理的&#xff0c;但没有爆出来&#xff0c;不知道问题在哪。 老老实实不用多线程&#xff0c;一个多小时觉得还是太累。 用的话&#xff0c;部…...

【java】力扣 反转字符串中的单词

目录 题目描述题目描述思路代码 题目描述 151.反转字符串中的单词 题目描述 思路 主要是利用快慢指针和字符串的截取 还要了解去掉首尾空格的函数是trim 那s"the sky is blue"举例 这个例子是没有首尾空格的&#xff0c;以防万一&#xff0c;我们不管有没有&#…...

科学设计程序员面试内容,破解“八股文”之弊

“八股文”在实际工作中是助力、阻力还是空谈&#xff1f; 作为现在各类大中小企业面试程序员时的必问内容&#xff0c;“八股文”似乎是很重要的存在。但“八股文”是否能在实际工作中发挥它“敲门砖”应有的作用呢&#xff1f;有IT人士不禁发出疑问&#xff1a;程序员面试考…...

蓝牙BlueZ验证使用记录

最近使用的一款AICSemi AIC8800D8芯片做的WiFiBT二合一模组&#xff0c;该模组WiFi使用SDIO通信&#xff0c;BT使用UART通信&#xff0c;供应商丢了一份驱动&#xff0c;包含了三个目录&#xff1a;aic8800_bsp、aic8800_fdrv和aic8800_btlpm&#xff0c;而蓝牙部分提供了lbh_s…...

【从0制作自己的ros导航小车:上位机篇】02、ros1多机通讯与坐标变换可视化

从0制作自己的ros导航小车 前言一、ros1多机通讯二、rviz可视化小车坐标系 前言 上节课完成了里程计数据与坐标变换发布&#xff0c;但是还没有测试&#xff0c;本节进行测试&#xff0c;测试之前需要知道一件事&#xff0c;上位机也就是开发板一般不做可视化用&#xff0c;因…...

JumpServer关闭admin mfa验证

背景 因为上一次启动了mfa验证&#xff0c;但是没有验证就关机重启&#xff0c;导致再开机输入密码后需要mfa绑定&#xff0c;但是怎么也无法绑定成功&#xff0c;导致无法登录。 故希望通过后台取消mfa的验证 解决方法 1. 进入docker docker exec -it jms_core /bin/bash…...

Kafka知识总结(选举机制+控制器+幂等性)

文章收录在网站&#xff1a;http://hardyfish.top/ 文章收录在网站&#xff1a;http://hardyfish.top/ 文章收录在网站&#xff1a;http://hardyfish.top/ 文章收录在网站&#xff1a;http://hardyfish.top/ 选举机制 控制器&#xff08;Broker&#xff09;选举 控制器就是…...

2024非常全的接口测试面试题及参考答案-软件测试工程师没有碰到算我输!

一、前言 接口测试最近几年被炒的火热了&#xff0c;越来越多的测试同行意识到接口测试的重要性。接口测试为什么会如此重要呢&#xff1f; 主要是平常的功能点点点&#xff0c;大家水平都一样&#xff0c;是个人都能点&#xff0c;面试时候如果问你平常在公司怎么测试的&#…...

python 写一个年会抽奖的demo

使用while 进行循环&#xff0c;进行三轮之后&#xff0c;停止。random.sample() 抽样不重复查询数据 import random name_list [] for i in range(0,100): name_list .append(员工{i}) winnerNum [30,6,3] //每个中奖人数 count 0 while count < 3: choice i…...

C++ OpenCV 实现多张图片叠加 叠加文字

C OpenCV 实现多张图片叠加 叠加文字 在C中使用OpenCV叠加多张图片以及添加文字的基本步骤如下&#xff1a; 加载多张图片。 确定叠加位置。 使用cv::addWeighted叠加图片&#xff0c;可以为叠加的图片添加透明度。 使用cv::putText在图片上添加文字。 显示或保存结果图片…...

用 apifox cli 命令行运行本地接口出现TypeError:Invalid IP address: undefined

用 apifox cli 命令行运行本地接口出现TypeError:Invalid IP address: undefined&#xff0c;客户端运行是通过的但命令行运行会报错 修改端口也是一样报错&#xff0c;地址修改为127.0.0.1会报错connect ECONNREFUSED 127.0.0.1:8080 解决方法&#xff1a;不用localhost&…...

PyQt6简易案例代码GUI界面小工具——实现增、删、查、改(练手正合适)

目录 专栏导读1、库的介绍PyQt6的主要特点包括&#xff1a;使用PyQt6开发应用程序的一般步骤&#xff1a;库的安装 2、设计窗口设计列表视图设计输入框控件与按钮设计布局listView的简单样式增删查改函数 完整代码总结 专栏导读 &#x1f338; 欢迎来到Python办公自动化专栏—P…...

JavaScript快速入门指南

JavaScript是一种广泛应用于网页开发的脚本语言&#xff0c;它可以让网页实现动态效果和交互性。无论是前端开发还是全栈开发&#xff0c;JavaScript都是不可或缺的一部分。本文将带你快速入门JavaScript&#xff0c;从基础语法到实际应用&#xff0c;让你快速上手这门强大的语…...

Esbuild介绍

Esbuild是一个由Evan Wallace基于Go语言开发的快速、可扩展的JavaScript和CSS打包器及压缩器。它以其极快的构建速度和高效的性能在众多构建工具中脱颖而出。 一、核心特性 超快的构建速度&#xff1a; Esbuild相比传统的构建工具&#xff08;如Webpack&#xff09;在构建速度…...

UnityShaderUI编辑器扩展

前言&#xff1a; 当我们在制作通用Shader的时候&#xff0c;避免不了许多参数混杂在一起&#xff0c;尽管在材质面板已经使用过Header标签来区分&#xff0c;但是较长的Shader参数就会导致冗余&#xff0c;功能块不够简约明了&#xff0c;如图&#xff1a; 对于Shader制作者来…...

分布式事务——2PC 代码示例

一 2PC代码示例 在Java中实现两阶段提交&#xff08;2PC, Two-Phase Commit&#xff09;协议通常涉及多个组件&#xff0c;包括事务协调者&#xff08;Transaction Coordinator&#xff09;和多个资源管理器&#xff08;Resource Managers&#xff0c;如数据库&#xff09;。在…...

vue实现简易的全局加载动画效果

效果展示 思路 封装一个组件&#xff0c;放Img&#xff0c;伪类样式&#xff0c;固定在屏幕fixed 然后App应用这个组件&#xff0c;Z index拉最大&#xff0c;防止用户在加载动画时乱点&#xff0c; v-show绑定loading&#xff0c;该数据可以放vuex还是任一的公共状态管理变…...

Linux网络工具“瑞士军刀“集合

一、背景 平常我们在进行Linux服务器相关运维的时候&#xff0c;总会遇到一些网络相关的问题。我们可以借助这些小巧、功能强悍的工具帮助我们排查问题、解决问题。 下面结合之前的一些使用经验为大家介绍一下一些经典应用场景下&#xff0c;这个网络命令工具如何使用的。例如怎…...

Sentinel隔离、降级、授权规则详解

文章目录 Feign整合Sentinel线程隔离熔断降级授权规则自定义异常结果 上一期教程讲解了 Sentinel 的限流规则&#xff1a; Sentinel限流规则&#xff0c;这一期主要讲述 Sentinel 的 隔离、降级和授权规则 虽然限流可以尽量避免因高并发而引起的服务故障&#xff0c;但服务还…...

C++11 列表初始化与类型声明

目录 ​ 0.前言 1.C11介绍 2.统一的列表初始化 2.1{}初始化 2.2initializer_list 2.2.1initializer_list 的基本用法 2.2.2用于类的 initializer_list 构造函数 2.2.3与标准库容器的结合 2.2.4优势与注意事项 3.新声明 3.1auto 3.1.1基本用法 3.1.2优势 3.1.3注意事项 3.2declt…...

缓存策略自定义:Laravel应用性能优化秘籍

缓存策略自定义&#xff1a;Laravel应用性能优化秘籍 在现代Web应用中&#xff0c;缓存是一种提高应用性能和响应速度的有效手段。Laravel框架提供了强大的缓存机制&#xff0c;支持多种缓存驱动&#xff0c;如文件、数据库、Redis等。然而&#xff0c;在某些情况下&#xff0…...

python连接sqlserver,封装操作

1封装 # 导入Flask类 import pymssql import tracebackclass Mssql(object):# 连接库def base(database):connect pymssql.connect(usersa,password123456,databasef{database},charsetutf8,as_dictTrue)if connect:print("数据库连接成功&#xff01;")else:print…...

原生PHP/JS自主开发的交友内核框架婚恋交友系统V10

本文来自&#xff1a;婚恋交友系统V10 - 源码1688 应用介绍 原生PHP/JS自主开发的交友内核框架&#xff0c;极高性能、无捆绑、自主权、无流水扣点、独立全开源 01脱单盲盒&#xff1a;脱单盲盒类似于漂流瓶&#xff0c;先将自己《投放》到盲盒中&#xff0c;另一伴有缘将您取…...

如何在Java、Python、GO程序中使用AI人脸识别API接口

AI人脸识别是一种通过面部识别或确认一个人身份的软件。它通过识别和测量图像中的面部特征来工作。面部识别可以识别图像或视频中的人脸&#xff0c;确定两幅图像中的人脸是否属于同一个人&#xff0c;或者在大量现有图像中搜索人脸。 AI人脸识别的优势是什么&#xff1f; 高…...

在vue使用MQTT

在vue中使用MQTT 最近有个需求&#xff0c;需要前端直接链接mqtt&#xff0c;想到后面可能多出使用&#xff0c;就封装成了hooks 中间遇到了一个坑&#xff0c;就是浏览器默认不支持mqtt协议&#xff0c;其借助了webSocket实现的mqtt协议&#xff0c; 而mqtt默认未开启webSocke…...

DNS、网关、IP、DHCP

DNS、网关、IP、DHCP&#xff1a;深入剖析与理解 在计算机网络的世界中&#xff0c;DNS、网关、IP和DHCP是四个至关重要的概念&#xff0c;它们共同构建了互联网的基础架构&#xff0c;确保了数据的准确传输和设备的有效连接。本文将深入剖析这四个概念&#xff0c;帮助读者更…...

vue2 vue3 props 的处理机制

在 Vue 2 中&#xff0c;props 是单向数据流&#xff0c;父组件向子组件传递的 props 默认情况下是不具有响应式特性的。这意味着当父组件的数据发生变化时&#xff0c;如果传递给子组件的 props 发生变化&#xff0c;子组件不会自动更新视图。 具体来说&#xff0c;在 Vue 2 …...

C++第十弹 ---- vector的介绍及使用

目录 前言vector的介绍及使用1. vector的使用1.1 vector的定义1.2 iterator的使用1.3 vector空间增长问题1.4 vector增删查改 2. vector迭代器失效问题(重点) 总结 前言 本文介绍了C中的vector数据结构及其使用方法。 更多好文, 持续关注 ~ 酷酷学!!! 正文开始 vector的介绍…...

ValueError: invalid literal for int() with base 10: ‘a‘

ValueError: invalid literal for int() with base 10: ‘a‘ 目录 ValueError: invalid literal for int() with base 10: ‘a‘ 【常见模块错误】 【解决方案】 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页&#xff0c;我是博主英杰&#xff…...

[C++探索]初始化列表,static成员,友元函数,内部类,匿名对象

&#x1f496;&#x1f496;&#x1f496;欢迎来到我的博客&#xff0c;我是anmory&#x1f496;&#x1f496;&#x1f496; 又和大家见面了 欢迎来到C探索系列 作为一个程序员你不能不掌握的知识 先来自我推荐一波 个人网站欢迎访问以及捐款 推荐阅读 如何低成本搭建个人网站…...

搭建自己的金融数据源和量化分析平台(二):读取上交所股票列表

我在上交所没发现上交所有像深交所一样的一键下载股票xls文档的按钮&#xff0c;因此上交所的股票列表读取就会比较麻烦。总体思路是查出来所有股票的代码之后根据股票代码逐一发起HTTP请求读取公司英文名、总股本、流通股本等详细信息&#xff0c;这就导致上交所爬虫的网络交互…...

Kafka知识总结(分区机制+压缩机制+拦截器+副本机制)

文章收录在网站&#xff1a;http://hardyfish.top/ 文章收录在网站&#xff1a;http://hardyfish.top/ 文章收录在网站&#xff1a;http://hardyfish.top/ 文章收录在网站&#xff1a;http://hardyfish.top/ 分区机制 分区策略 分区策略是决定生产者将消息发送到哪个分区的…...

WordPress原创插件:搜索引擎抓取首图seo图片

WordPress原创插件&#xff1a;搜索引擎抓取首图seo图片 插件设置 插件将在网站头部添加适当的meta标签&#xff0c;以便百度等搜索引擎抓取指定的固定图像。 插件下载 https://download.csdn.net/download/huayula/89596527...

Android Framework 之AMS

它管理了系统的四大组件:Activity、Service、ContentProvider、Broadcast。 它除了管理四大组件外&#xff0c;同时也负责管理和调度所有的进程 AMS相关目录结构 AMS代码主要在下面几个目录(AndroidQ上AMS相关部分功能移到了wm下)&#xff1a; frameworks/base/core/java/andro…...

AnConda环境配置学习笔记

AnConda环境配置 个人笔记&#xff0c;自己学习使用。 1、软件安装 去官网或者是清华大学镜像下载 2、环境配置 Conda 查看版本&#xff1a;conda --version 更新所有库 conda update --all&#xff08;千万不要跟新&#xff0c;版本不匹配&#xff09; matploitlib安装cond…...

架构师的36项修炼 学习笔记

架构师的36项修炼 学习笔记 分布式缓存 缓存特点 1.技术简单 2.性能提升明显 3.应用场景多 缓存数据存储 hash表 缓存的关键指标 命中率 缓存失效方式 超时失效 LLT 实时清除 代理缓存 反向代理缓存 多层反向代理缓存 内容分发网络CDN 通读缓存 包括代理缓存…...

Python | “IndexError: tuple index out of range” 【已解决】

Python | “IndexError: tuple index out of range” 【已解决】 IndexError: tuple index out of range 深度解析与实战指南 在Python编程中&#xff0c;IndexError: tuple index out of range是一个常见的错误&#xff0c;它发生在尝试访问元组&#xff08;或其他可索引的数…...

Linux上部署easySpider及基本使用

一、安装及简介 默认使用Chrome浏览器。 1、下载压缩包 官网&#xff1a;易采集EasySpider&#xff1a;无代码可视化爬虫/浏览器自动化测试软件 Linux版只适用于Ubuntu 20.04及以上版本、Deepin、Debian及其衍生版本。 &#xff08;建议使用&#xff09;下载网址/Github下…...

Qt Designer,仿作一个ui界面的练习(二):部件内容的填充

有了完成了布局的基本框架设计之后&#xff0c;对各个部件逐步完成内容的填充。 一、还是从顶边栏开始&#xff1a; 1、在顶边栏的topLogo里面拖入一个QLabel&#xff08;标签&#xff09;&#xff0c;命名为logoImage&#xff0c;删除标签的文字。 2、右键点击topLogo&#x…...

LIS2DH12传感器底电流100ua处理

默认已经正常初始化IIC和LIS2DH12之后&#xff0c;需要正常开启和进入低功耗传感器的处理。 主要是对两个寄存器的处理&#xff1a;20、1E ODR[3&#xff1a;0]数据速率选择。默认值&#xff1a;0000&#xff08;0000&#xff1a;断电模式&#xff1b;其他&#xff1a;见表31&a…...

五、Spring Boot - 上手篇(1)

&#x1f33b;&#x1f33b;目录 一、快速入门&#xff1a;创建第一个SpringBoot 工程1.1 点击File--->New--->Project...1.2 选择版本和依赖的相关骨架包1.3 设置项目保存目录1.4 项目创建完成&#xff0c;工程主界面如下1.5 项目说明1.6 启动项目1.7 编写 HelloControl…...

Spring -- 使用XML开发MyBatis

T04BF &#x1f44b;专栏: 算法|JAVA|MySQL|C语言 &#x1faf5; 今天你敲代码了吗 文章目录 MyBatis XML配置文件开发配置连接字符串和MyBatis写Mapper层代码添加mapper接口添加UserInfoXmLMapper.xml 操作数据库INSERTDELETE & UPDATE MyBatis XML配置文件开发 实际上,除…...

openmv 学习笔记(24电赛笔记)

寻找特定目标 这个功能主要应用在&#xff0c;机器人寻找色块&#xff0c;无人机跟踪特定颜色&#xff0c;生产线上检测物体进行分类&#xff0c;还有人机交互等等功能应用。 相关函数 image.find_blobs(thresholds, roiAuto, x_stride2, y_stride1, invertFalse, area_thr…...

【C语言】【数据结构】二分查找(数组的练习)

目录 一、什么是二分查找 二、算法思想 2.1、概述 2.2、举例 &#xff08;1&#xff09;查找3&#xff08;数组里面存在的数&#xff09; &#xff08;2&#xff09;查找12&#xff08;数组里面不存在的数&#xff09; 三、代码实现 四、计算mid公式的优化 一、…...

Web:Url 编码 -13

URL编码概述 HTTP协议只支持iso8859-1字符集。 而此字符集中只有英文数字常见符号。 所以HTTP原生是无法传输非iso8859-1字符的。 为了解决这个问题&#xff0c;提出了一种称之为URL编码的解决方案。 URL编解码详解 将非iso8859-1字符&#xff0c;进行转换 先将字符按照指定码表…...

typescript 引用数据类型

let arr1: number[] [1, 2, 3]; // 规定为数组数字 let arr2: (number | string)[] ["1", 2, 3]; // 数字或字符串 &#xff5c;就代表联合类型 也称元组 let arr3: [null, string] [null, "1"]; // 必须两个值&#xff1a;null和字符串 let arr4: […...

OpenCV库学习之cv2.Sobel函数

OpenCV库学习之cv2.Sobel函数 一、简介 cv2.Sobel是OpenCV库中用于边缘检测的函数。它基于Sobel算子&#xff0c;通过计算图像在水平和垂直方向上的一阶导数来检测边缘。Sobel算子是一种离散差分算子&#xff0c;能够有效地突出图像中的高频变化区域&#xff0c;即边缘。 二、…...

上传Git 仓库 勤勉git (超详细教程)

注册 官网&#xff1a; 我就喜欢动个仓库名字和分支名字 就创建了...

C/C++基础:宏

C/C基础&#xff1a;宏 简述宏的简单使用基础语法带参宏&#xff08;宏函数&#xff09;宏参字符串化#宏拼接## 宏的陷阱多行定义宏中的空格宏函数不是函数行末分号问题一些建议 宏的奇妙使用 简述 宏作为C/C最有特色的语言性质之一&#xff0c;犹如魔法一般&#xff0c;合理的…...