Rumah  >  Artikel  >  pangkalan data  >  Analisis contoh perusahaan log masuk SMS menggunakan Redis

Analisis contoh perusahaan log masuk SMS menggunakan Redis

PHPz
PHPzke hadapan
2023-05-27 16:19:061018semak imbas

1. Import Projek Kajian Kuda Hitam

Projek Kajian Kuda Hitam terutamanya merangkumi fungsi berikut:

Analisis contoh perusahaan log masuk SMS menggunakan Redis

1

projek yang diperlukan Mesej peribadi kepada saya dengan maklumat

Jadualnya ialah:

  • tb_user: Jadual pengguna

  • tb_user_info : Jadual butiran pengguna

  • tb_shop: Jadual maklumat saudagar

  • tb_shop_type: Jadual jenis saudagar

  • tb_blog: Jadual diari pengguna ( Diari lawatan kedai Master)

  • tb_follow: Senarai ikut pengguna

  • tb_voucher: Senarai kupon

  • tb_voucher_order: Jadual pesanan kupon

Nota: Versi Mysql mengguna pakai versi 5.7 dan ke atas

Analisis contoh perusahaan log masuk SMS menggunakan Redis

2. Pemisahan bahagian hadapan dan hujung belakang

Analisis contoh perusahaan log masuk SMS menggunakan Redis

3. Import projek hujung belakang

3.1 Import projek hujung belakang ke Idea

Analisis contoh perusahaan log masuk SMS menggunakan Redis

3.2 Nota: Ubah suai maklumat alamat mysql dan redis dalam fail application.yaml kepada maklumat anda sendiri

Analisis contoh perusahaan log masuk SMS menggunakan Redis

3.3 Mulakan projek Selepas memulakan projek, lawati: http://localhost:8081/shop-type/list dalam pelayar Jika anda dapat melihat data, ia membuktikan bahawa tiada masalah dengan operasi

Analisis contoh perusahaan log masuk SMS menggunakan Redis

4. Import projek bahagian hadapan

4.1 Import folder nginx Salin folder nginx ke mana-mana direktori Pastikan direktori itu tidak mengandungi aksara khas Cina dan ruang, contohnya:

Analisis contoh perusahaan log masuk SMS menggunakan Redis

4.2 Jalankan projek front-end dalam direktori tempat nginx berada, dan masukkan arahan untuk memulakan nginx:

start nginx.exe

Analisis contoh perusahaan log masuk SMS menggunakan Redis

Buka penyemak imbas chrome dan klik tetikus pada halaman kosong Klik kanan dan pilih Inspect untuk membuka alatan pembangun:

Analisis contoh perusahaan log masuk SMS menggunakan Redis

Kemudian lawati: http://127.0.0.1:8080 dan anda akan melihat halaman:

Analisis contoh perusahaan log masuk SMS menggunakan Redis

2. Laksanakan proses log masuk berdasarkan Sesi

Analisis contoh perusahaan log masuk SMS menggunakan Redis

  • Halaman belakang akan menyimpan kod pengesahan yang dijana dan maklumat pengguna ke sesi, dan mengembalikan sessionId ke bahagian hadapan dan menyimpannya dalam kuki

  • Apabila pengguna log masuk, kuki akan dibawa untuk membuat permintaan ke bahagian belakang Apabila bahagian belakang melakukan pengesahan, ia akan diperolehi daripada kuki sessionId, melalui sessionId, maklumat pengguna boleh diperoleh daripada sesi dan disimpan dalam ThreadLocal

  • Setiap urutan berikutnya akan mempunyai salinan maklumat pengguna dalam ThreadLocal yang berbeza boleh mendapatkan maklumat pengguna Laksanakan operasi yang berbeza untuk mencapai pengasingan benang

1 Hantar kod pengesahan SMS

Analisis contoh perusahaan log masuk SMS menggunakan Redis

Kod utama:

@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {

    @Resource
    private IUserService userService;

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

2. Log masuk dan pendaftaran kod pengesahan SMS

Analisis contoh perusahaan log masuk SMS menggunakan Redis

Kod utama:

UserController

@Service
@Slf4j
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);
        // 返回ok
        return Result.ok();
    }
}

UserServiceImpl

rreee 🎜>3. Fungsi pengesahan log masuk

Apabila pengguna meminta untuk log masuk, kuki akan dibawa, dan kuki mengandungi JSEESIONID

Analisis contoh perusahaan log masuk SMS menggunakan RedisUntuk elakkan mengesahkan maklumat pengguna setiap kali apabila pengguna meminta setiap pengawal, anda boleh menambah pemintas

Pemintas hanya perlu mengesahkan sekali dan menyimpan maklumat pengguna apabila pengguna meminta akses kepada ThreadLocal untuk digunakan oleh urutan berikutnya

Analisis contoh perusahaan log masuk SMS menggunakan Redis

Kod utama:

Tulis ThreadLocal dalam kelas alat

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

Tulis pemintas log masuk dalam kelas alat

@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
    // 1.校验手机号
    String phone = loginForm.getPhone();
    if (RegexUtils.isPhoneInvalid(phone)) {
        // 如果不符合,返回错误信息
        return Result.fail("手机号格式错误!");
    }

    // 2.校验验证码
    Object cacheCode = session.getAttribute("code");
    String code = loginForm.getCode();
    if (cacheCode == null || !cacheCode.toString().equals(code)) {
        // 3.验证码不一致,则报错
        return Result.fail("验证码错误");
    }

    // 4.验证码一致,根据手机号查询用户
    User user = query().eq("phone", phone).one();

    // 5.判断用户是否存在
    if (user == null) {
        // 6.用户不存在,则创建用户并保存
        user = createUserWithPhone(phone);
    }

    // 7.保存用户信息到session中,UserDTO只包含简单的用户信息,
    // 而不是完整的User,这样可以隐藏用户的敏感信息(例如:密码等),还能减少内存使用
    session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));

    // 8.返回ok
    return Result.ok();
}

private User createUserWithPhone(String phone) {
    // 1.创建用户
    User user = new User();
    user.setPhone(phone);
    // 随机设置昵称 user_mrkuw05lok
    user.setNickName(SystemConstants.USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));
    // 2.保存用户
    save(user);
    return user;
}

Tambahkan kelas konfigurasi pemintas dalam kelas konfigurasi

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();
    }
}

UserController

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

三、集群的session共享问题

Analisis contoh perusahaan log masuk SMS menggunakan Redis

四、基于Redis实现共享session的登录功能

1. 选择合适的数据结构存入Redis

  • 手机号作为key,String类型的验证码作为value

  • 用户登录时正好会提交手机号,方便通过Redis进行校验验证码

Analisis contoh perusahaan log masuk SMS menggunakan Redis

token作为key,Hash类型的用户信息作为value

后端校验成功后,会返回token给前端,前端会将token保存到sessionStorage中(这是浏览器的存储方式),以后前端每次请求都会携带token,方便后端通过Redis校验用户信息

Analisis contoh perusahaan log masuk SMS menggunakan Redis

前端代码:将后端返回的token保存到sessionStorage中

Analisis contoh perusahaan log masuk SMS menggunakan Redis

前端每次请求时,都会通过拦截器将token设置到请求头中,赋值给变量authorization,后端通过authorization获取前端携带的token进行校验

Analisis contoh perusahaan log masuk SMS menggunakan Redis

2. 发送短信验证码

修改之前代码,将验证码存入Redis

@Service
@Slf4j
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
        // "login:code:"是业务前缀,以"login:code:" + 手机号为key,过期时间2分钟
        stringRedisTemplate.opsForValue().set(RedisConstants.LOGIN_CODE_KEY + phone, code, RedisConstants.LOGIN_CODE_TTL, TimeUnit.MINUTES);

        // 5.模拟发送验证码
        log.debug("发送短信验证码成功,验证码:{}", code);
        // 返回ok
        return Result.ok();
    }
}

3. 短信验证码登录、注册

  • 修改之前代码,从Redis获取验证码并校验

  • 随机生成token,保存用户信息到redis中,返回token

@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
    // 1.校验手机号
    String phone = loginForm.getPhone();
    if (RegexUtils.isPhoneInvalid(phone)) {
        // 如果不符合,返回错误信息
        return Result.fail("手机号格式错误!");
    }

//        // 2.校验验证码
//        Object cacheCode = session.getAttribute("code");
//        String code = loginForm.getCode();
//        if (cacheCode == null || !cacheCode.toString().equals(code)) {
//            // 3.验证码不一致,则报错
//            return Result.fail("验证码错误");
//        }

    // 2.从Redis获取验证码并校验
    String cacheCode = stringRedisTemplate.opsForValue().get(RedisConstants.LOGIN_CODE_KEY + phone);
    String code = loginForm.getCode();
    if (cacheCode == null || !cacheCode.equals(code)) {
        // 3.验证码不一致,则报错
        return Result.fail("验证码错误");
    }

    // 4.验证码一致,根据手机号查询用户
    User user = query().eq("phone", phone).one();

    // 5.判断用户是否存在
    if (user == null) {
        // 6.用户不存在,则创建用户并保存
        user = createUserWithPhone(phone);
    }

//        // 7.保存用户信息到session中,UserDTO只包含简单的用户信息,而不是完整的User,这样可以隐藏用户的敏感信息(例如:密码等),还能减少内存使用
//        session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));

    // 7.保存用户信息到redis中
    // 7.1随机生成token,作为登录令牌
    // 使用hutool工具中的UUID,true表示不带“-”符号的UUID
    String token = UUID.randomUUID().toString(true);
    
    // 7.2将User对象转为Hash类型进行存储
    UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);   
    // 由于使用的是stringRedisTemplate,所以存入的value中的值必须都是String类型的
    // 但是UserDTO中的id是Long类型的,所以进行对象属性拷贝时,需要自定义实现转换规则
     Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),CopyOptions.create().setIgnoreNullValue(true).setIgnoreNullValue(true).setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));
                                                     
    // 7.3存入redis, "login:token:"是业务前缀,以 "login:token:" + token作为key
    stringRedisTemplate.opsForHash().putAll(RedisConstants.LOGIN_USER_KEY + token, userMap);
    // 7.4设置token有效期,有效期为30分钟
    stringRedisTemplate.expire(RedisConstants.LOGIN_USER_KEY + token, RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);

    // 8.返回token
    return Result.ok(token);
}

private User createUserWithPhone(String phone) {
    // 1.创建用户
    User user = new User();
    user.setPhone(phone);
    // 随机设置昵称 user_mrkuw05lok
    user.setNickName(SystemConstants.USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));
    // 2.保存用户
    save(user);
    return user;
}

4. 解决token刷新问题

  • token刷新问题是指,用户长时间不进行界面操作时,到了过期时间,token自动失效;但是,用户一旦进行操作,就需要给token续期,即更新token过期时间

  • 为了解决token刷新问题,需要加2个拦截器

  • 第一个拦截器可以拦截所有请求,只要用户有请求就刷新token,并保存用户信息到ThreadLocal中

  • 第二个拦截器只对登录请求进行拦截,从ThreadLocal中获取用户信息进行校验

Analisis contoh perusahaan log masuk SMS menggunakan Redis

刷新token的拦截器代码:

public class RefreshTokenInterceptor implements HandlerInterceptor {

    // 因为LoginInterceptor不是通过Spring进行管理的Bean,所以不能再LoginInterceptor中进行注入StringRedisTemplate
    // 可以通过构造方法传入StringRedisTemplate
    private StringRedisTemplate stringRedisTemplate;

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

    /**
     * 前置拦截
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//        // 1.获取session
//        HttpSession session = request.getSession();
//        // 2.获取session中的用户
//        Object user = session.getAttribute("user");
//        // 3.判断用户是否存在
//        if(user == null){
//            // 4.不存在,拦截,返回401状态码
//            response.setStatus(401);
//            return false;
//        }
//        // 5.存在,保存用户信息到ThreadLocal
//        UserHolder.saveUser((UserDTO)user);
//        // 6.放行
//        return true;

        // 1.获取请求头中的token
        String token = request.getHeader("authorization");
        if (StrUtil.isBlank(token)) {
            // 不存在,则拦截,返回401状态码
            response.setStatus(401);
            return false;
        }

        // 2.通过token获取redis中的用户
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash()
            .entries(RedisConstants.LOGIN_USER_KEY + token);

        // 3.判断用户是否存在
        if (userMap.isEmpty()) {
            // 4.用户不存在,则拦截,返回401状态码
            response.setStatus(401);
            return false;
        }

        // 5.将redis中Hash类型数据转换成UserDTO对象
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);

        // 6.用户存在,保存用户信息到ThreadLocal
        UserHolder.saveUser(userDTO);

        // 7.刷新token有效期
        stringRedisTemplate.expire(RedisConstants.LOGIN_USER_KEY + token, RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);

        // 8.放行
        return true;
    }

    /**
     * 后置拦截器
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
        Object handler, Exception ex) throws Exception {
        // 请求结束后移除用户,防止ThreadLocal造成内存泄漏
        UserHolder.removeUser();
    }
}

登录拦截器的代码:

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;
        }
        // 有用户,则放行
        return true;
    }

}
@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 添加拦截器
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 登录拦截器
        registry.addInterceptor(new LoginInterceptor())
            // 排除不需要拦截的路径
            .excludePathPatterns(
                "/shop/**",
                "/voucher/**",
                "/shop-type/**",
                "/upload/**",
                "/blog/hot",
                "/user/code",
                "/user/login"
            ).order(1);

        // token刷新的拦截器,order越小,执行优先级越高,所以token刷新的拦截器先执行
        registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**")
            .excludePathPatterns(
            // RefreshTokenInterceptor拦截器也需要放行"/user/code","/user/login",不然token过期后再重新登录就会一直被拦截
                "/user/code",
                "/user/login")
            .order(0);
    }
}

Atas ialah kandungan terperinci Analisis contoh perusahaan log masuk SMS menggunakan Redis. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Artikel ini dikembalikan pada:yisu.com. Jika ada pelanggaran, sila hubungi admin@php.cn Padam