1. 优客号首页
  2. 用户投稿

Springboot使用redis实现接口Api限流的实例

Springboot使用redis实现接口Api限流的实例前言该篇介绍的内容如题,就是利用redis实现接口的限流( 某时间范围内 最大的访问次数 ) 。正文 惯例,先看下我们的实战目录结构:首先是pom.xml 核心依赖:

前言

Springboot使用redis实现接口Api限流的实例

该篇介绍的内容如题,就是利用redis实现接口的限流(  某时间范围内 最大的访问次数 ) 。

正文 

惯例,先看下我们的实战目录结构:

Springboot使用redis实现接口Api限流的实例

首先是pom.xml 核心依赖:

<!–用于redis数据库连接–> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!–用于redis lettuce 连接池pool使用–> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>

然后是application.yml里面的redis接入配置:

spring: redis: lettuce: pool: #连接池最大连接数 使用负值代表无限制 默认为8 max-active: 10 #最大空闲连接 默认8 max-idle: 10 #最小空闲连接 默认0 min-idle: 1 host: 127.0.0.1 password: 123456 port: 6379 database: 0 timeout: 2000msserver: port: 8710

redis的配置类, RedisConfig.java:

ps:可以看到日期是18年的,因为这些redis的整合教程,在这个系列里面一共有快10篇,不了解的看客如果感兴趣可以去看一看。

import com.fasterxml.jackson.annotation.JsonAutoDetect;import com.fasterxml.jackson.annotation.PropertyAccessor;import com.fasterxml.jackson.databind.ObjectMapper;import org.springframework.cache.CacheManager;import org.springframework.cache.annotation.EnableCaching;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.data.redis.cache.RedisCacheConfiguration;import org.springframework.data.redis.cache.RedisCacheManager;import org.springframework.data.redis.connection.RedisConnectionFactory;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.core.StringRedisTemplate;import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;import org.springframework.data.redis.serializer.RedisSerializationContext;import org.springframework.data.redis.serializer.StringRedisSerializer; import static org.springframework.data.redis.cache.RedisCacheConfiguration.defaultCacheConfig; /** * @Author: JCccc * @CreateTime: 2018-09-11 * @Description: */@Configuration@EnableCachingpublic class RedisConfig { @Bean public CacheManager cacheManager(RedisConnectionFactory connectionFactory) { RedisCacheConfiguration cacheConfiguration = defaultCacheConfig() .disableCachingNullValues() .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new Jackson2JsonRedisSerializer(Object.class))); return RedisCacheManager.builder(connectionFactory).cacheDefaults(cacheConfiguration).build(); } @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(factory); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); //序列化设置 ,这样为了存储操作对象时正常显示的数据,也能正常存储和获取 redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); redisTemplate.setHashKeySerializer(new StringRedisSerializer()); redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer); return redisTemplate; } @Bean public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) { StringRedisTemplate stringRedisTemplate = new StringRedisTemplate(); stringRedisTemplate.setConnectionFactory(factory); return stringRedisTemplate; }}

自定义注解:

import java.lang.annotation.*; /** * @Author JCccc * @Description * @Date 2021/7/23 11:46 */@Inherited@Documented@Target({ElementType.FIELD, ElementType.TYPE, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)public @interface RequestLimit { /** * 时间内 秒为单位 */ int second() default 10; /** * 允许访问次数 */ int maxCount() default 5; //默认效果 : 10秒内 对于使用该注解的接口,只能总请求访问数 不能大于 5次 }

接下来是拦截器 RequestLimitInterceptor.java:

拦截接口的方式 是通过 ip地址+接口url ,做时间内的访问计数

import com.elegant.testdemo.annotation.RequestLimit;import com.elegant.testdemo.utils.IpUtil;import com.fasterxml.jackson.databind.ObjectMapper;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.stereotype.Component;import org.springframework.web.method.HandlerMethod;import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.util.concurrent.TimeUnit; /** * @Author JCccc * @Description * @Date 2021/7/23 11:49 */ @Componentpublic class RequestLimitInterceptor implements HandlerInterceptor { private final Logger log = LoggerFactory.getLogger(this.getClass()); @Autowired private RedisTemplate<String, Object> redisTemplate; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { try { if (handler instanceof HandlerMethod) { HandlerMethod handlerMethod = (HandlerMethod) handler; // 获取RequestLimit注解 RequestLimit requestLimit = handlerMethod.getMethodAnnotation(RequestLimit.class); if (null==requestLimit) { return true; } //限制的时间范围 int seconds = requestLimit.second(); //时间内的 最大次数 int maxCount = requestLimit.maxCount(); String ipAddr = IpUtil.getIpAddr(request); // 存储key String key = ipAddr+”:”+request.getContextPath() + “:” + request.getServletPath(); // 已经访问的次数 Integer count = (Integer) redisTemplate.opsForValue().get(key); log.info(“检测到目前ip对接口={}已经访问的次数”, request.getServletPath() , count); if (null == count || -1 == count) { redisTemplate.opsForValue().set(key, 1, seconds, TimeUnit.SECONDS); return true; } if (count < maxCount) { redisTemplate.opsForValue().increment(key); return true; } log.warn(“请求过于频繁请稍后再试”); returnData(response); return false; } return true; } catch (Exception e) { log.warn(“请求过于频繁请稍后再试”); e.printStackTrace(); } return true; } public void returnData(HttpServletResponse response) throws IOException { response.setCharacterEncoding(“UTF-8”); response.setContentType(“application/json; charset=utf-8”); ObjectMapper objectMapper = new ObjectMapper(); //这里传提示语可以改成自己项目的返回数据封装的类 response.getWriter().println(objectMapper.writeValueAsString(“请求过于频繁请稍后再试”)); return; } }

接下来是 拦截器的配置 WebConfig.java:

import com.elegant.testdemo.interceptor.RequestLimitInterceptor;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * @Author JCccc * @Description * @Date 2021/7/23 11:52 */ @Configurationpublic class WebConfig implements WebMvcConfigurer { @Autowired private RequestLimitInterceptor requestLimitInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(requestLimitInterceptor) //拦截所有请求路径 .addPathPatterns(“/**”) //再设置 放开哪些路径 .excludePathPatterns(“/static/**”,”/auth/login”); } }

最后还有两个工具类

IpUtil:

https://www.jb51.net/article/218249.htm

RedisUtil :

https://www.jb51.net/article/218246.htm

最后写个测试接口

TestController.java 

import com.elegant.testdemo.annotation.RequestLimit;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController; /** * @Author JCccc * @Description * @Date 2021/7/23 11:55 */@RestControllerpublic class TestController { @GetMapping(“/test”) @RequestLimit(maxCount = 3,second = 60) public String test() { return “你好,如果对你有帮助,请点赞加关注。”; } }

这个/test接口的注解,我们设置的是 60秒内 最大访问次数为 3次 (实际应用应该是根据具体接口做相关的次数限制。)

然后使用postman测试一下接口:

前面三次都是请求通过的:

Springboot使用redis实现接口Api限流的实例 

Springboot使用redis实现接口Api限流的实例

 第四次:

Springboot使用redis实现接口Api限流的实例

到此这篇关于Springboot使用redis实现接口Api限流的实例的文章就介绍到这了,更多相关Springboot redis接口Api限流内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!

spring boot应用场景是什么?做APP后端接口合适吗

app

Spring Security 接口认证鉴权入门实践指南

Web API 接口服务场景里,用户的认证和鉴权是很常见的需求,Spring Security 据说是这个领域里事实上的标准,实践下来整体设计上确实有不少可圈可点之处,也在一定程度上印证了小伙们经常提到的 “太复杂了” 的说法也是很有道理的。 本文以一个简单的 SpringBoot Web 以应用为例,重点介绍以下内容: 创建 SpringBoot 示例,用于演示 Spring Security 在 SpringBoot 环境下的应用,简要介绍四部分内容:pom.xml、application.yml、IndexController 和 HelloController。

SpringBoot 应用名称为 example,实例端口为 9999 。 IndexController 实现一个接口:/。 HelloController 实现两个接口:/hello/world 和 /hello/name。 编译启动 SpringBoot 应用,通过浏览器请求接口,请求路径和响应结果: SpringBoot 示例准备完成。

SpringBoot 集成 Spring Security 仅需要在 pom.xml 中添加相应的依赖: spring-boot-starter-security ,如下: 编译启动应用,相对于普通的 SpringBoot 应用,我们可以在命令行终端看到 特别 的两行日志: 表示 Spring Security 已在 SpringBoot 应用中生效。 默认 情况下,Spring Security 自动化 地帮助我们完成以下三件事件: 使用 Spring Security 默认为我们生成的用户名和密码进行登录(Sign in),成功之后会自动重定向至 / : 之后我们就可以通过浏览器正常请求 /hello/world 和 /hello/name。 默认情况下,Spring Security 仅支持基于 FormLogin 方式的认证,只能使用固定的用户名和随机生成的密码,且不支持鉴权。

如果想要使用更丰富的安全特性: 本文以 Java Configuration 的方式为例进行介绍,需要我们提供一个继承自 WebSecurityConfigurerAdapter 配置类,然后通过重写若干方法进而实现自定义配置。 SecurityConfig 使用 @Configuration 注解(配置类),继承自 WebSecurityConfigurerAdapter ,本文通过重写 configure 方法实现自定义配置。 需要注意: WebSecurityConfigurerAdapter 中有多个名称为 configure 重载方法,这里使用的是参数类型为 HttpSecurity 的方法。

注:Spring Security 默认自动化配置参考 Spring Boot Auto Configuration 。 用以指定哪些请求需要什么样的认证或授权,这里使用 anyRequest() 和 authenticated() 表示所有的请求均需要认证。 表示我们使用 HttpBasic 认证。

编译启动应用,会发现终端仍会输出密码: 因为,我们仅仅改变的是认证方式。 为方便演示,我们使用 CURL 直接请求接口: 会提示我们 Unauthorized ,即:没有认证。 我们按照 HttpBasic 要求添加请求头部参数 Authorization ,它的值: 即: 再次请求接口: 认证成功,接口正常响应。 使用默认用户名和随机密码的方式不够灵活,大部分场景都需要我们支持多个用户,且分别为他们设置相应的密码,这就涉及到两个问题: 对于 读取 ,Spring Security 设计了 UserDetailsService 接口: 实现按照用户名(username)从某个存储介质中加载相对应的用户信息(UserDetails)。

用户名,客户端发送请求时写入的用于用户名。 用户信息,包括用户名、密码、权限等相关信息。 注意:用户信息不只用户名和用户密码。 对于 存储 ,Spring Security 设计了 UserDetailsManager 接口: 创建用户信息 修改用户信息 删除用户信息 修改当前用户的密码 检查用户是否存在 注意: UserDetailsManager 继承自 UserDetailsService 。

也就是说,我们可以通过提供一个已实现接口 UserDetailsManager * 的类,并重写其中的若干方法,基于某种存储介质,定义用户名、密码等信息的存储和读取逻辑;然后将这个类的实例以 Bean 的形式注入 Spring Security,就可以实现用户名和密码的自定义。 实际上,Spring Security 仅关心如何 读取 , 存储 可以由业务系统自行实现;相当于,只实现接口 UserDetailsService 即可。 Spring Security 已经为我们预置了两种常见的存储介质实现: InMemoryUserDetailsManager和 JdbcUserDetailsManager 均实现接口 UserDetailsManager ,本质就是对于 UserDetails 的 CRUD 。我们先介绍 UserDetails ,然后再分别介绍基于内存和数据库的实现。

UserDetails是用户信息的抽象接口: 获取用户名。 获取密码。 获取权限,可以简单理解为角色名称(字符串),用于实现接口基于角色的授权访问,详情见后文。

获取用户是否可用,或用户/密码是否过期或锁定。 Spring Security 提供了一个 UserDetails 的实现类 User ,用于用户信息的实例表示。另外, User 提供 Builder 模式的对象构建方式。

设置用户名称。 设置密码,Spring Security 不建议使用明文字符串存储密码,密码格式: 其中,id 为加密算法标识,encodedPassword 为密码加密后的字符串。这里以加密算法 bcrypt 为例,详细内容可参考 Password Storage 。 设置角色,支持多个。

UserDetails实例创建完成之后,就可以使用 UserDetailsManager 的具体实现进行存储和读取。 InMemoryUserDetailsManager是 Spring Security 为我们提供的基于内存实现的 UserDetailsManager 。 使用 @Bean 将 InMemoryUserDetailsManager 实例注入 Spring Security。

创建 InMemoryUserDetailsManager 实例之后,并不是必须立即调用 createUser 添加用户信息,也可以在业务系统的其它地方获取已注入的 InMemoryUserDetailsManager 动态存储 UserDetails 实例。 编译启动应用,使用我们自己创建的用户名和密码(userA/123456)访问接口: 基于内存介质自定义的用户名和密码已生效,接口正常响应。 JdbcUserDetailsManager是 Spring Security 为我们提供的基于数据库实现的 UserDetailsManager ,相较于 InMemoryUserDetailsManager 使用略复杂,需要我们创建数据表,并准备好数据库连接需要的数据源(DataSource), JdbcUserDetailsManager 实例的创建依赖于数据源。 JdbcUserDetailsManager可以与业务系统共用一个数据库数据源实例,本文不讨论数据源的相关配置。

以 MySQL 为例,创建数据表语句: 其他数据库语句可参考 User Schema 。 JdbcUserDetailsManager实例的创建与注入,除 之外,整体流程与 InMemoryUserDetailsManager 类似,不再赘述。 在业务系统中获取已注入的 JdbcUserDetailsManager 实例,可以动态存储 UserDetails 实例。

编译启动应用,使用我们自己创建的用户名和密码(userA/123456)访问接口: 基于数据库介质自定义的用户名和密码已生效,接口正常响应。 Spring Security 可以提供基于角色的权限控制: 假设,存在两个角色 USER(普通用户) 和 ADMIN(管理员), 角色 USER 可以访问接口 /hello。

Springboot 使用AOP实现防止接口重复提交

在传统的web项目中,防止重复提交,通常做法是:后端生成一个唯一的提交令牌(uuid),并存储在服务端。页面提交请求携带这个提交令牌,后端验证并在第一次验证后删除该令牌,保证提交请求的唯一性。

无需前端配合,纯后端处理,是最清爽的。设计思路如下: 自定义注解@RreventReSubmit标记所有Controller中的提交请求。通过AOP 对所有标记@RreventReSubmit的方法拦截。在业务方法执行前,获取当前用户的 token(或者JSessionId)+ 当前请求地址,作为一个唯一 KEY,去获取 Redis 分布式锁(如果此时并发获取,只有一个线程会成功获取锁)。

当有请求调用接口时,到redis中查找相应的key,如果能找到,则说明重复提交,如果找不到,则执行操作。业务方法执行后,释放锁。 切面类需要使用@Aspect和@Component这两个注解做标注。

在想要防止重复提交的接口上添加注解即可使用。

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

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

发表评论

登录后才能评论