Redis实战——短信登录

news2025/1/14 18:01:19

目录

1 基于Seesion实现短信登录

1.1 发送短信验证码 

 1.2 登录功能

2 使用Redis进行短信验证码校验登录

2.1 Seesion方法存在的问题

2.2 发送短信验证码

2.3  验证码校验及登录功能

 3.拦截器优化


1 基于Seesion实现短信登录

1.1 发送短信验证码 

发送验证码请求路径 /user/code

Controller层

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

Service层及Service实现

public interface IUserService extends IService<User> {

    Result sendCode(String phone, HttpSession session);


}
package com.hmdp.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.dto.LoginFormDTO;
import com.hmdp.dto.Result;
import com.hmdp.dto.UserDTO;
import com.hmdp.entity.User;
import com.hmdp.mapper.UserMapper;
import com.hmdp.service.IUserService;
import com.hmdp.utils.RegexPatterns;
import com.hmdp.utils.RegexUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.manager.util.SessionUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpSession;

import static com.hmdp.utils.SystemConstants.USER_NICK_NAME_PREFIX;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author 虎哥
 * @since 2021-12-22
 */
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {


    @Override
    public Result sendCode(String phone, HttpSession session) {
        //1.校验手机号
        if (RegexUtils.isPhoneInvalid(phone)){
            //2.如果错误,返回错误信息
            return Result.fail("手机号码格式错误!");
        }

        //3.生成验证码
        String code = RandomUtil.randomNumbers(6);

        //4.保存验证码到session中去
        session.setAttribute("code",code);

        //5.发送验证码
        log.debug("手机验证码为:{}",code);

        return Result.ok();
    }

}

 1.2 登录功能

请求路径:user/login

 

Controller层


    /**
     * 登录功能
     * @param loginForm 登录参数,包含手机号、验证码;或者手机号、密码
     */
    @PostMapping("/login")
    public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){
        // TODO 实现登录功能
        return userService.login(loginForm,session);
    }

 Service层及Service实现

public interface IUserService extends IService<User> {

    Result login(LoginFormDTO loginForm, HttpSession session);

}
package com.hmdp.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.dto.LoginFormDTO;
import com.hmdp.dto.Result;
import com.hmdp.dto.UserDTO;
import com.hmdp.entity.User;
import com.hmdp.mapper.UserMapper;
import com.hmdp.service.IUserService;
import com.hmdp.utils.RegexPatterns;
import com.hmdp.utils.RegexUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.manager.util.SessionUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpSession;

import static com.hmdp.utils.SystemConstants.USER_NICK_NAME_PREFIX;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author 虎哥
 * @since 2021-12-22
 */
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {


    @Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {

        String phone = loginForm.getPhone();

        //1.校验手机号
        if (RegexUtils.isPhoneInvalid(phone)){
            //2.如果错误,返回错误信息
            return Result.fail("手机号码格式错误!");
        }

        //2.校验验证码
        String code = loginForm.getCode();
        String sendCode = (String) session.getAttribute("code");
        if (code != null || !code.equals(sendCode)){
            //验证码不一致
            Result.fail("验证码错误!");
        }
        //验证码相同,查询手机号
        User user = query().eq("phone", loginForm.getPhone()).one();

        if (user == null) {
            //用户不存在,创建一个新用户并且保存到数据库中
            user = createNewUser(phone);

        }

        //用户存在,存入Session
        session.setAttribute("user", BeanUtil.copyProperties(user,UserDTO.class));

        return Result.ok();
    }

    private User createNewUser(String phone) {
        //1.创建用户
        User user = new User();
        user.setPhone(phone);
        user.setNickName(USER_NICK_NAME_PREFIX+RandomUtil.randomString(10));
        //2.保存用户
        save(user);
        return user;
    }
}

1.3 登录校验功能(配置拦截器)

 

    

编写拦截器类:

package com.hmdp.utils;

import com.hmdp.dto.UserDTO;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/**
 * @Author 华子
 * @Date 2022/11/20 17:30
 * @Version 1.0
 */
public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        //1.获session
        HttpSession session = request.getSession();

        //2.获取用户信息
        Object user = session.getAttribute("user");

        //3.判断用户信息是否存在
        if (user == null){
            //4.不存在,拦截
            response.setStatus(401);
            return false;
        }

        //5.存在,保存用户信息到ThreadLocal
        UserHolder.saveUser((UserDTO) user);

        //6.放行
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //移除用户
        UserHolder.removeUser();
    }
}

配置添加拦截器到SpringMvc中(配置拦截路径)

package com.hmdp.config;

import com.hmdp.utils.LoginInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @Author 华子
 * @Date 2022/11/20 17:38
 * @Version 1.0
 */
@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
        .excludePathPatterns(
                "/shop/**",
                "/voucher/**",
                "/shop-type/**",
                "/upload/**",
                "/blog/hot",
                "/user/code",
                "/user/login"
        );
    }
}

最后Controller层返回用户信息

@GetMapping("/me")
    public Result me(){
        // TODO 获取当前登录的用户并返回
        UserDTO user = UserHolder.getUser();
        return Result.ok(user);
    }

2 使用Redis进行短信验证码校验登录

2.1 Seesion方法存在的问题

session共享问题

多台Tomcat并不共享session存储空间,当请求切换到不同tomcat服务时导致数据丢失的问题。 

session的替代方案应该满足:

数据共享

内存存储

key、value结构

无可厚非,就说我们的Reids啦~

2.2 发送短信验证码

这步操作和之前的Session很相同,只是我们这次把验证码保存到Redis当中

 

对于验证码的保存,我们选择String类型操作,key用手机号(唯一性)value就是验证码

 

代码:(其余操作一样,就是验证码本来是存到Session中,改为存到Reids) 

 

package com.hmdp.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.dto.LoginFormDTO;
import com.hmdp.dto.Result;
import com.hmdp.dto.UserDTO;
import com.hmdp.entity.User;
import com.hmdp.mapper.UserMapper;
import com.hmdp.service.IUserService;
import com.hmdp.utils.RegexPatterns;
import com.hmdp.utils.RegexUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.manager.util.SessionUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpSession;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import static com.hmdp.utils.RedisConstants.*;
import static com.hmdp.utils.SystemConstants.USER_NICK_NAME_PREFIX;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author 虎哥
 * @since 2021-12-22
 */
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {


    @Resource
    private StringRedisTemplate stringRedisTemplate;


    @Override
    public Result sendCode(String phone, HttpSession session) {
        //1.校验手机号
        if (RegexUtils.isPhoneInvalid(phone)){
            //2.如果错误,返回错误信息
            return Result.fail("手机号码格式错误!");
        }

        //3.生成验证码
        String code = RandomUtil.randomNumbers(6);

        //4.保存验证码到session中去
//        session.setAttribute("code",code);
        //4.保存验证码到redis中去
        stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY+phone,code,LOGIN_CODE_TTL, TimeUnit.MINUTES);

        //5.发送验证码
        log.debug("手机验证码为:{}",code);

        return Result.ok();
    }

}

2.3  验证码校验及登录功能

这一步明显要比上一步复杂的多了,我们一步一步来

首先,就是用户每次访问的时候,每次的路径都是不一样的,而Seesion是从Cookie中获取唯一的SessionId来确保唯一性,以此取得用户值,而对于Redis,我们及需要一个唯一的标识符,就是我们的Token(使用UUID生成的随机字符串)

和之前的Session操作不同的地方在于

1. 使用手机号为key去Redis中获取验证码

2. 使用随机生成的字符串Token保存用户数据

3.返回Token数据给前端,由前端保存到请求头的“Authorization”中 

温馨小提示:这里我们使用Map操作来保存用户数据~

具体实现代码如下:

package com.hmdp.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.dto.LoginFormDTO;
import com.hmdp.dto.Result;
import com.hmdp.dto.UserDTO;
import com.hmdp.entity.User;
import com.hmdp.mapper.UserMapper;
import com.hmdp.service.IUserService;
import com.hmdp.utils.RegexPatterns;
import com.hmdp.utils.RegexUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.manager.util.SessionUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpSession;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import static com.hmdp.utils.RedisConstants.*;
import static com.hmdp.utils.SystemConstants.USER_NICK_NAME_PREFIX;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author 虎哥
 * @since 2021-12-22
 */
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {


    @Resource
    private StringRedisTemplate stringRedisTemplate;


    @Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {

        String phone = loginForm.getPhone();

        //1.校验手机号
        if (RegexUtils.isPhoneInvalid(phone)){
            //2.如果错误,返回错误信息
            return Result.fail("手机号码格式错误!");
        }

        //2.校验验证码
        //2.1 从redis中获取验证码
        String sendCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);
        String code = loginForm.getCode();
//        String sendCode = (String) session.getAttribute("code");
        if (code != null || !code.equals(sendCode)){
            //验证码不一致
            Result.fail("验证码错误!");
        }
        //验证码相同,查询手机号
        User user = query().eq("phone", loginForm.getPhone()).one();

        if (user == null) {
            //用户不存在,创建一个新用户并且保存到数据库中
            user = createNewUser(phone);

        }
        // 用户存在,将用户数据存入Redis
        //随机生成一个token
        String token = UUID.randomUUID().toString(true);

        //获取用户数据并转成Map集合
        UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);

        Map<String, Object> userMap = BeanUtil.beanToMap(userDTO,new HashMap<>(),
                CopyOptions.create()
                        .setIgnoreNullValue(true)
                        .setFieldValueEditor((fieldName,fieldValue)->fieldValue.toString())
        );

        //将用户数据存入redis当中
        String tokenKey = LOGIN_USER_KEY+token;
        stringRedisTemplate.opsForHash().putAll(tokenKey,userMap);

        //设置有效期,防止用户过多,导致内存不足
        stringRedisTemplate.expire(tokenKey,LOGIN_USER_TTL,TimeUnit.MINUTES);


//        session.setAttribute("user", BeanUtil.copyProperties(user,UserDTO.class));

        return Result.ok(token);
    }

    private User createNewUser(String phone) {
        //1.创建用户
        User user = new User();
        user.setPhone(phone);
        user.setNickName(USER_NICK_NAME_PREFIX+RandomUtil.randomString(10));
        //2.保存用户
        save(user);
        return user;
    }
}

还没完,Session操作中,我们用户每次请求,都会重新设置存活时间,以确保用户可以持续,而我们使用Redis时,可以在拦截器中设置每次拦截到,重新设置Redis字段的存活时间 。

和Session使用的拦截器不同点在于

1.我们要先获取请求头中的token,用来获取用户数据

2.保存完用户数据后,我们需要重新设置Redis中key的存活时间

具体代码如下:

package com.hmdp.utils;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.hmdp.dto.UserDTO;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @Author 华子
 * @Date 2022/11/20 17:30
 * @Version 1.0
 */
public class RefreshTokenInterceptor implements HandlerInterceptor {

    private StringRedisTemplate stringRedisTemplate;

    public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        //1.获取请求头中的token
        String token = request.getHeader("authorization");
        if (StrUtil.isBlank(token)) {
            return true;
        }

        //2.获取用户信息
        String key = RedisConstants.LOGIN_USER_KEY + token;
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);

        //3.判断用户信息是否存在
        if (userMap.isEmpty()){
            return true;
        }

        //5.存在,保存用户信息到ThreadLocal
        //将从redis中获取到的userMap转成UserDto
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
        UserHolder.saveUser(userDTO);

        //6.刷新存活时间
        stringRedisTemplate.expire(key,RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);

        //7.放行
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //移除用户
        UserHolder.removeUser();
    }
}

Mvc配置类(MvcConfig):

package com.hmdp.config;

import com.hmdp.utils.LoginInterceptor;
import com.hmdp.utils.RefreshTokenInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import javax.annotation.Resource;

/**
 * @Author 华子
 * @Date 2022/11/20 17:38
 * @Version 1.0
 */
@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
        .excludePathPatterns(
                "/shop/**",
                "/voucher/**",
                "/shop-type/**",
                "/upload/**",
                "/blog/hot",
                "/user/code",
                "/user/login"
        );
    }
}

这里有个小知识点,我们自己创造的类,Spring无法帮我们自动注入,所以我们在Mvc配置类中进行注入 

 

这样就可以成功的注入StringRedisTemplate了~ 

 3.拦截器优化

因为我们上述的拦截器只拦截了部分请求,还没做到百分百刷新用户的存活时间,所以我们可以这样做:

在最外层在加上一个拦截器,拦截所有用户,用于专门刷新存活时间且放行,而第二个拦截器用于判断ThreadLocal中是否含有用户信息,没有就拦截。

 

具体实现代码:

最外层拦截器:(只做刷新)

package com.hmdp.utils;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.hmdp.dto.UserDTO;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @Author 华子
 * @Date 2022/11/20 17:30
 * @Version 1.0
 */
public class RefreshTokenInterceptor implements HandlerInterceptor {

    private StringRedisTemplate stringRedisTemplate;

    public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        //1.获取请求头中的token
        String token = request.getHeader("authorization");
        if (StrUtil.isBlank(token)) {
            return true;
        }

        //2.获取用户信息
        String key = RedisConstants.LOGIN_USER_KEY + token;
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);

        //3.判断用户信息是否存在
        if (userMap.isEmpty()){
            return true;
        }

        //5.存在,保存用户信息到ThreadLocal
        //将从redis中获取到的userMap转成UserDto
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
        UserHolder.saveUser(userDTO);

        //6.刷新存活时间
        stringRedisTemplate.expire(key,RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);

        //7.放行
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //移除用户
        UserHolder.removeUser();
    }
}

第二次拦截器:(只做拦截)

package com.hmdp.utils;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.hmdp.dto.UserDTO;
import io.netty.util.internal.StringUtil;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @Author 华子
 * @Date 2022/11/20 17:30
 * @Version 1.0
 */
public class LoginInterceptor implements HandlerInterceptor {



    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        //1.判断ThreadLocal中是否存在用户信息
       if (UserHolder.getUser() == null){
           //没有,拦截
           response.setStatus(401);
           return false;
       }
        //2. 有,放行
        return true;
    }


}

Mvc配置类:

这里面主要设置了添加拦截器,设置第二个拦截器需要拦截的路径 和两个拦截器的优先级。

package com.hmdp.config;

import com.hmdp.utils.LoginInterceptor;
import com.hmdp.utils.RefreshTokenInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import javax.annotation.Resource;

/**
 * @Author 华子
 * @Date 2022/11/20 17:38
 * @Version 1.0
 */
@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
        .excludePathPatterns(
                "/shop/**",
                "/voucher/**",
                "/shop-type/**",
                "/upload/**",
                "/blog/hot",
                "/user/code",
                "/user/login"
        ).order(1);
        registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).order(0);
    }
}

 

 

 

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

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

相关文章

2、skywalking-安装(Docker-Compose方式)

1、服务器环境介绍 两台服务器均为Centos7.6 172.16.128.129 vm1 装载skywalking(采用es作为数据库) 172.16.128.130 vm2 项目服务器&#xff0c;然后向vm1集成 2、准备工作 2.1、由于安装skywalking是通过docker-compose方式安装在vm1且项目到时候是直接build成镜像然后发布&a…

行业安全解决方案 | 零售企业如何做好安全建设对抗黑灰产?

随着各行各业信息化、数字化、智能化进程不断加快&#xff0c;零售行业新业态也正在蓬勃发展。然而&#xff0c;随着转型深入推进及业务量的上升&#xff0c;随处可见的安全威胁逐渐成为零售企业的首要难题。 其主要原因在于该类企业一般涵盖大量用户个人数据&#xff0c;当企…

2022,软件测试行业岗位细分,薪资分布

软件测试是个需求多&#xff0c;就业机会大的职业。目前&#xff0c;我国具备软件测试能力的人员数量和市场需求相差巨大&#xff0c;巨大的市场空缺&#xff0c;使软件测试工程师从初级到高级&#xff0c;只需要 1 年甚至更短的时间来完成。所以作为一名软件测试工程师&#x…

图像基础知识、深度学习基础知识以及相关问题

疑难问题总结第一部分&#xff1a;图像基础边缘和轮廓1、图像中&#xff0c;什么是高频域和低频域&#xff1f;2、什么是图像轮廓&#xff0c;什么是图像边缘&#xff1f;第二部分&#xff1a;深度学习第一部分&#xff1a;图像基础 边缘和轮廓 1、图像中&#xff0c;什么是高…

从一座瑞典风机的倒塌看VDI2230用于螺栓连接精确计算的重要性

作者&#xff1a;螺栓设计老张 一、写在前面 引言&#xff1a;在机械行业&#xff0c;螺栓是与轴承、齿轮齐名的三大最主要机械元素&#xff0c;而从应用广泛程度来看&#xff0c;相比于轴承和齿轮是有过之而无不及。无论是机械传动还是机械结构&#xff0c;都离不开螺栓&…

[附源码]Python计算机毕业设计成绩管理与学情分析系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

WPS—JS宏笔记记录

前言&#xff1a;本方法需要一定的JS基础&#xff0c;这边不会赘述&#xff0c;0基础者可先自学JS 文章目录官方参考文档壹——excel表格篇一、创建新的表格输入内容并保存关闭ThisWorkbook&#xff1a;Application.Path:Workbooks:workbooks.Add:Sheets&#xff1a;自测&#…

OpenCV-Python小应用(五):基于模板匹配的图像拼接

OpenCV-Python小应用&#xff08;五&#xff09;&#xff1a;基于模板匹配的图像拼接前言前提条件实验环境基于模板匹配的图像拼接参考文献前言 本文是个人使用OpenCV-Python的应用案例&#xff0c;由于水平有限&#xff0c;难免出现错漏&#xff0c;敬请批评改正。更多精彩内容…

【微服务】分布式组件 Nacos 结合 Feign 的使用

本文主要介绍如何搭建分布式开发基本环境 一、基本概念 1. 注册中心 在分布式系统中&#xff0c;每一个微服务上线&#xff0c;都需要注册到注册中心。&#xff08;方便服务的远程调用&#xff0c;比如订单想调用商品服务&#xff0c;直接从注册中心获得&#xff09; 对应 Sp…

SpringBoot整合mybatis

SpringBoot整合mybatis 以tb_book表格为例&#xff1a; 第一步&#xff1a;创建新模块&#xff0c;选择Spring初始化&#xff0c;并配置模块相关基础信息 第二步&#xff1a;选择当前模块需要使用的技术集&#xff08;MyBatis、MySQL&#xff09; 或者手工导入对应技术的star…

idea创建纯净的maven项目简单的maven工程

idea创建简单的maven项目一、说在前面二、创建步骤一、说在前面 在学习或者开发中&#xff0c;有时候&#xff0c;我们只想创建一个简单的maven工程&#xff0c;不需要有太多的自带的配置或配置文件&#xff0c;本文结合这一需求&#xff0c;将创建步骤分享给大家&#xff0c;…

Python Selenium unittest+HTMLTestRunner实现 自动化测试及发送测试报告邮件

1、UI测试框架搭建-目录结构 2、 文件介绍 2.1、baseinfo->__init__.py 配置文件定义基础参数 #-*-coding:utf-8-*- #测试用例配置参数base_url "http://xxxxx.com" undirect_username "username" undirect_password "password" direct_…

想知道怎么给图片加贴纸?手把手教你给图片加贴纸

有时候我们在拍摄照片时&#xff0c;会不小心排到一些隐私的东西&#xff0c;这个时候该怎么办呢&#xff1f;可能很多人会先想到使用马赛克涂抹&#xff0c;这个方法确实好&#xff0c;但马赛克在帮我们遮挡的同时&#xff0c;也会影响到图片的整体观感。那我们应该用什么来代…

WebRTC Pacer

目录 一. 前言 二. WebRTC Pacer 1. 数据包传入Pacer模块的队列 2. Pacer模块取出队列的包发送 &#xff08;1&#xff09;什么时候取出数据包发送 &#xff08;2&#xff09;每次发送多少数据量 &#xff08;3&#xff09;避免引入较大延时的处理方法 一. 前言 实时音视…

@MapperScan 和 @Mapper 源码走读

一.从开发中遇到的问题开始 问题描述 : 在一个springbootmybatis的项目中,在dao也就是Mapper接口上配置了Mapper注解&#xff0c;其他同事在启动类还配置了MapperScan注解&#xff08;包扫描没有配全面&#xff09;&#xff0c;进行批量指定所生成的Mapper接口动态代理接口类&…

TFT-LCD移植LVGL详细过程记录

TFT-LCD移植LVGL LVGL(轻量级和通用图形库)是一个免费和开源的图形库&#xff0c;它提供了创建嵌入式GUI所需的一切&#xff0c;具有易于使用的图形元素&#xff0c;美丽的视觉效果和低内存占用。 LVGL更多介绍&#xff1a;https://zhuanlan.zhihu.com/p/406294618 本次实验…

第六章 图论 16 AcWing 1558. 加油站

第六章 图论 16 AcWing 1558. 加油站 原题链接 AcWing 1558. 加油站 算法标签 图论 最短路 枚举 思路 枚举加油站位置&#xff0c;对于每个加油站位置进行dijkstra&#xff0c;选择符合要求1的最小距离最大值&#xff08;要求2&#xff09;与的距离和最小值&#xff08;要…

(九)Java算法:快速排序(详细图解)

目录一、前言1.1、概念1.2、算法过程二、maven依赖三、流程解析3.1、全部数据分区3.2、左边数据分区3.3、右边数据分区四、编码实现结语一、前言 1.1、概念 快速排序&#xff1a;用数组的第一个数作为基准数据&#xff0c;然后将所有比它小的数都放到它左边&#xff0c;所有比…

使用 Spring Boot 设置 Hibernate Envers

Hibernate Envers是一个实现持久实体的审核和版本控制的模块。审计和版本控制是构建生产级Spring 启动微服务的关键组件。Hibernate Envers与Spring Boot无缝集成以实现相同的目标。 在这篇文章中&#xff0c;我们将在我们的Spring Boot Starter应用程序中集成Hibernate Envers…

生信工作流框架搭建 | 02-nextflow 实战

目录生信工作流框架搭建 | 02-nextflow前情提要开始使用依赖安装核心概念一个fastqc的示例&#xff0c;加深理解快速搭建你的程序你需要仔细阅读的&#xff1a;可以快速浏览&#xff08;但需要知道大概有什么&#xff0c;以便后来查览&#xff09;&#xff1a;报错&#xff01;…