项目实训-工具类Redisson限流与Md5工具

创新项目实训

任务

我们需要控制用户使用系统的次数,避免超支,即需要限流

限流
  1. 限流算法参考网站https://juejin.cn/post/6967742960540581918

  2. 使用Redission限流

    • 什么是Redission
      • Redisson是一个开源的Java客户端库,它提供了一套丰富的分布式同步原语和数据结构,全部构建在Redis之上。Redisson不仅仅是一个客户端,它还抽象了许多高级功能,使得开发者可以更轻松地在分布式环境中实现常见的协调任务,如信号量、屏障、计数器、锁、队列等。
    • 引入依赖
  <dependency>
             <groupId>org.springframework.session</groupId>
             <artifactId>spring-session-data-redis</artifactId>
         </dependency>
         <!--https://github.com/redisson/redisson#quick-start-->
         <!-- 在Redis的基础上实现的Java驻内存数据网格-->
         <dependency>
             <groupId>org.redisson</groupId>
             <artifactId>redisson</artifactId>
             <version>3.31.0</version>
         </dependency>	
  • 设置properties
spring.redis.host=
spring.redis.port=6379
spring.redis.database=1
spring.redis.timeout=5000
spring.redis.password=
  1. 创建RedissonConfig配置类,用于初始化RedissonClient对象单例
 import lombok.Data;
 import org.redisson.Redisson;
 import org.redisson.api.RedissonClient;
 import org.redisson.config.Config;
 import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 
 @Configuration
 @ConfigurationProperties(prefix = "spring.redis")
 @Data
 public class RedissonConfig {
 
     private Integer database;
 
     private String host;
 
     private Integer port;
 
     private String password;
 
     @Bean
     public RedissonClient redissonClient() {
         Config config = new Config();
         config.useSingleServer()
                 .setDatabase(database)
                 .setAddress("redis://" + host + ":" + port)
                 .setPassword(password);
         RedissonClient redisson = Redisson.create(config);
         return redisson;
     }
 }
  1. 编写RedisLimiterManager专门提供RedisLimiter限流基础服务,提供了通用的能力。
import com.example.demo.utils.BusinessException;
import com.example.demo.utils.ErrorCode;
import org.redisson.api.RRateLimiter;
import org.redisson.api.RateIntervalUnit;
import org.redisson.api.RateType;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
* 专门提供 RedisLimiter 限流基础服务的(提供了通用的能力)
*/
@Service
public class RedisLimiterManager {

 @Resource
 private RedissonClient redissonClient;

 /**
  * 限流操作
  *
  * @param key 区分不同的限流器,比如不同的用户 id 应该分别统计
  */
 public void doRateLimit(String key) {
     // 创建一个名称为user_limiter的限流器,每秒最多访问 2 次
     RRateLimiter rateLimiter = redissonClient.getRateLimiter(key);
     rateLimiter.trySetRate(RateType.OVERALL, 2, 1, RateIntervalUnit.SECONDS);
     // 每当一个操作来了后,请求一个令牌
     boolean canOp = rateLimiter.tryAcquire(1);
     if (!canOp) {
         throw new BusinessException(ErrorCode.TOO_MANY_REQUEST);
     }
 }
}

RedisLimiterManager 类被 @Service注解标记,表明这是一个由Spring管理的服务组件。
它持有一个RedissonClient实例,这是用于与Redis服务器进行交互的客户端。
doRateLimit方法中,执行以下操作:

  • 使用给定的key从RedissonClient获取一个速率限制器实例。
  • 调用trySetRate方法设置速率限制器的规则:每秒允许的最大请求数为2次。
  • 使用tryAcquire方法尝试获取一个令牌,如果当前的请求超过了设定的速率,则返回false,表示不能继续操作。
  • 如果无法获取令牌(即canOp为false),则抛出BusinessException异常,错误码为TOO_MANY_REQUEST,这通常意味着请求过多,需要客户端稍后再试。

这种实现方式可以有效地防止短时间内大量请求对系统的冲击,保护系统资源不被过度消耗,同时也避免了可能的恶意攻击或滥用行为。

  1. 编写测试用例
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;

@SpringBootTest
class RedisLimiterManagerTest {

    @Resource
    private RedisLimiterManager redisLimiterManager;

    @Test
    void doRateLimit() throws InterruptedException {
        String userId = "1";
        for (int i = 0; i < 2; i++) {
            redisLimiterManager.doRateLimit(userId);
            System.out.println("成功");
        }
        Thread.sleep(1000);
        for (int i = 0; i < 5; i++) {
            redisLimiterManager.doRateLimit(userId);
            System.out.println("成功");
        }
    }
}

  1. 运行测试

请添加图片描述

MD5工具类
  1. 引入依赖

    <!--引入Md5工具 -->
            <dependency>
                <groupId>commons-codec</groupId>
                <artifactId>commons-codec</artifactId>
            </dependency>
            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-lang3</artifactId>
                <version>3.12.0</version>
            </dependency>
    
  2. 编写Md5Util

    package com.example.demo.utils;
    
    import org.apache.commons.codec.digest.DigestUtils;
    
    import java.util.Random;
    
    public class Md5Util {
        /**
         * 加密方法
         * @param str
         * @return
         */
        public static String md5(String str){
            return DigestUtils.md5Hex(str);
        }
    
        /**
         * 加密
         * 将随机生成的盐和加盐后的MD5码,并将盐混入到M5码中,对MD5密码进行加强
         * @param password
         * @return
         */
        public static String generateSaltPassword(String password){
            Random random=new Random();
            /**
             * 生成一个16位的随机数,也就是盐值
             */
            StringBuilder stringBuilder=new StringBuilder(16);
            stringBuilder.append(random.nextInt(99999999)).append(random.nextInt(99999999));
            int len=stringBuilder.length();
            if(len<16){
                for(int i=0;i<16-len;i++){
                    stringBuilder.append("0");
                }
            }
            //生成盐
            String salt =stringBuilder.toString();
            //将盐加入到明文中,并生成新的Md5码
            password=md5(password+salt);
            //将盐混入到新生成的MD5码中,方便后期解密,校验密码
            char[] cs=new char[48];
            for(int i=0;i<48;i+=3){
                cs[i]=password.charAt(i/3*2);
                char c=salt.charAt(i/3);
                cs[i+1]=c;
                cs[i+2]=password.charAt(i/3*2+1);
            }
            return new String(cs);
    
        }
    
        /**
         * 解密
         * 将混入MD5加密的密文中的盐取出来,然后将传来的密码按照此盐进行MD5加密,然后比较
         * @param password  传回来的密码
         * @param md        数据库存的md5码
         * @return
         */
        public static boolean verifySaltPassword(String password,String md){
            //先从MD5码中取出之前加的盐和加盐后生成的MD5码
            char[] cs1=new char[32];
            char[] cs2=new char[16];
            for(int i=0;i<48;i+=3){
                cs1[i/3*2]=md.charAt(i);
                cs1[i/3*2+1]=md.charAt(i+2);
                cs2[i/3]=md.charAt(i+1);
            }
            String salt=new String(cs2);
            return md5(password+salt).equals(new String(cs1));
        }
    
    }
    
    • md5(String str)方法接收一个字符串,使用Apache Commons Codec库的DigestUtils.md5Hex()函数将其转换为MD5哈希值,增强数据安全性。

    • generateSaltPassword(String password) 方法生成一个16位随机数作为“盐”,与原始密码拼接后进行MD5加密,再将盐混合在生成的MD5码中,进一步提高密码的安全性,防止彩虹表攻击。

    • verifySaltPassword(String password, String md) 方法用于验证用户提供的密码是否正确。它从已加密的MD5码中分离出盐,使用相同的盐对用户提供的密码再次进行MD5加密,如果结果与数据库存储的MD5码匹配,则验证成功。

    • 加盐和混合盐的技术可以有效防止常见的密码破解技术,如字典攻击和彩虹表攻击,增加密码安全性。

    请添加图片描述

  • 20
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值