创新项目实训
任务
我们需要控制用户使用系统的次数,避免超支,即需要限流
限流
-
限流算法参考网站https://juejin.cn/post/6967742960540581918
-
使用Redission限流
- 什么是Redission
- Redisson是一个开源的Java客户端库,它提供了一套丰富的分布式同步原语和数据结构,全部构建在Redis之上。Redisson不仅仅是一个客户端,它还抽象了许多高级功能,使得开发者可以更轻松地在分布式环境中实现常见的协调任务,如信号量、屏障、计数器、锁、队列等。
- 引入依赖
- 什么是Redission
<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=
- 创建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;
}
}
- 编写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,这通常意味着请求过多,需要客户端稍后再试。
这种实现方式可以有效地防止短时间内大量请求对系统的冲击,保护系统资源不被过度消耗,同时也避免了可能的恶意攻击或滥用行为。
- 编写测试用例
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("成功");
}
}
}
- 运行测试
MD5工具类
-
引入依赖
<!--引入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>
-
编写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码匹配,则验证成功。
-
加盐和混合盐的技术可以有效防止常见的密码破解技术,如字典攻击和彩虹表攻击,增加密码安全性。
-