前言:
当前我们对“java中默认访问权限”大致比较看重,兄弟们都需要学习一些“java中默认访问权限”的相关文章。那么小编在网络上收集了一些有关“java中默认访问权限””的相关文章,希望你们能喜欢,各位老铁们一起来学习一下吧!1.如何对同一IP访问同一接口进行每秒、每分钟、每小时频率限制
话不多说,直接开干,首先写一个注解类
import java.lang.annotation.*;/** * 接口限流 * @author rs * */@Inherited@Documented@Target({ElementType.FIELD,ElementType.TYPE,ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)public @interface VisitLimit { //标识 指定sec时间段内的访问次数限制 int limit() default 5; //标识 时间段 long sec() default 5;}复制代码
使用注解的原因是:我们使用拦截器在请求处理之前,检查某个请求接口是否有该注解,如果有该注解,获取访问次数和时间段(比如:在1s中只能访问一次)。接下来我们就来写一个拦截器
import org.test.annotation.VisitLimit;import org.test.exception.BusinessException;import org.test.redis.RedisCache;import org.test.service.redis.RedisService;import org.test.util.IPUtils;import org.test.util.RedisUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import org.springframework.web.method.HandlerMethod;import org.springframework.web.servlet.ModelAndView;import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;import javax.servlet.ServletOutputStream;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.lang.reflect.Method;@Componentpublic class VisitLimitInterceptor extends HandlerInterceptorAdapter { @Autowired private RedisUtils redisService; /** * 处理请求之前被调用 * @param request * @param response * @param handler * @return * @throws Exception */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (handler instanceof HandlerMethod) { HandlerMethod handlerMethod = (HandlerMethod) handler; Method method = handlerMethod.getMethod(); if (!method.isAnnotationPresent(VisitLimit.class)) { return true; } VisitLimit accessLimit = method.getAnnotation(VisitLimit.class); if (accessLimit == null) { return true; } int limit = accessLimit.limit(); long sec = accessLimit.sec(); String key = IPUtils.getIpAddr(request) + request.getRequestURI(); Integer maxLimit =null; Object value =redisService.get(key); if(value!=null && !value.equals("")) { maxLimit = Integer.valueOf(String.valueOf(value)); } if (maxLimit == null) { redisService.set(key, "1", sec); } else if (maxLimit < limit) { Integer i = maxLimit+1; redisService.set(key, i.toString(), sec); } else {// output(response, "请求太频繁!");// return false; throw new BusinessException(500,"请求太频繁!"); } } return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { }复制代码
这里用到了redis,解释一下redis的key(IP+URL)记录了某个ip访问某个接口,value存的是访问的次数,加上一个过期时间,过期时间就是我们在注解上赋值的值。
这里的redis的部分代码也贴出来
@Servicepublic class RedisUtils { @Resource private RedisTemplate redisTemplate; /** * 写入缓存设置时效时间 * * @param key * @param value * @param expireTime 有效时间,单位秒 * @return */ public boolean set(final String key, Object value, Long expireTime) { boolean result = false; try { ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue(); operations.set(key, value); redisTemplate.expire(key, expireTime, TimeUnit.SECONDS); result = true; } catch (Exception e) { e.printStackTrace(); } return result; }}复制代码
#怎么获取用户的真实IP呢???如下
import org.slf4j.Logger;import org.slf4j.LoggerFactory;import javax.servlet.http.HttpServletRequest;/** * IP Utils * @author rs * */public class IPUtils { private static Logger logger = LoggerFactory.getLogger(IPUtils.class); /** * 获取IP地址 * * 使用Nginx等反向代理软件, 则不能通过request.getRemoteAddr()获取IP地址 * 如果使用了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP地址,X-Forwarded-For中第一个非unknown的有效IP字符串,则为真实IP地址 */ public static String getIpAddr(HttpServletRequest request) { String ip = null; try { ip = request.getHeader("x-forwarded-for"); if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) { // 多次反向代理后会有多个ip值,第一个ip才是真实ip if( ip.indexOf(",")!=-1 ){ ip = ip.split(",")[0]; } } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("HTTP_CLIENT_IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("HTTP_X_FORWARDED_FOR"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("X-Real-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } } catch (Exception e) { logger.error("IPUtils ERROR ", e); } return ip; } }复制代码
下面来正式使用一下
@VisitLimit(limit = 1, sec = 1)@RequestMapping(value = "/close", method = RequestMethod.POST)复制代码
这种方式不能很好的应对突发请求,需要对这一类情形平滑处理,比如200ms处理一个请求,下面就到令牌桶出场了!
二、令牌桶实战介绍
2.1 先来个总结吧,让大家分清什么时候用令牌桶,什么时候用漏桶
令牌桶:生产一个令牌消费一个漏桶: 处理大流量,并且以固定的速度平滑处理
使用场景:geteway网关
Bucket4j是基于令牌桶算法实现
package org.test.gateway.filter.limit;import java.time.Duration;import java.util.Map;import java.util.concurrent.ConcurrentHashMap;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.cloud.gateway.filter.GatewayFilter;import org.springframework.cloud.gateway.filter.GatewayFilterChain;import org.springframework.core.Ordered;import org.springframework.core.io.buffer.DataBuffer;import org.springframework.http.HttpHeaders;import org.springframework.http.server.reactive.ServerHttpRequest;import org.springframework.http.server.reactive.ServerHttpResponse;import org.springframework.stereotype.Component;import org.springframework.util.StringUtils;import org.springframework.web.server.ServerWebExchange;import io.github.bucket4j.Bandwidth;import io.github.bucket4j.Bucket;import io.github.bucket4j.Bucket4j;import io.github.bucket4j.Refill;import reactor.core.publisher.Mono;@Componentpublic class RateLimitByIpFilter implements GatewayFilter, Ordered { private final static Logger logger = LoggerFactory.getLogger(RateLimitByIpFilter.class); private int capacity; private int refillTokens; private Duration refillDuration; private static final Map<String, Bucket> CACHE = new ConcurrentHashMap<>(); public RateLimitByIpFilter() { } public RateLimitByIpFilter(int capacity, int refillTokens, Duration refillDuration) { this.capacity = capacity; this.refillTokens = refillTokens; this.refillDuration = refillDuration; } @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { String ip = getIpAddr(exchange.getRequest()); if (ip.indexOf("192.168") != -1 || ip.indexOf("172.31.202") != -1) { return chain.filter(exchange); } Bucket bucket = CACHE.computeIfAbsent(ip, k -> createNewBucket()); logger.info("限频来访ip: " + ip + ", 可用令牌数量:" + bucket.getAvailableTokens()); if (bucket.tryConsume(1)) { return chain.filter(exchange); } else { // 不合法(超过限流) ServerHttpResponse response = exchange.getResponse(); // 设置headers HttpHeaders httpHeaders = response.getHeaders(); httpHeaders.add("Content-Type", "application/json; charset=UTF-8"); httpHeaders.add("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0"); // 设置body String warningStr = "{\"code\":\"500\",\"message\":\"超过限流\"}"; DataBuffer bodyDataBuffer = response.bufferFactory().wrap(warningStr.getBytes()); return response.writeWith(Mono.just(bodyDataBuffer)); } } @Override public int getOrder() { return -1000; } private Bucket createNewBucket() { Refill refill = Refill.of(refillTokens, refillDuration); Bandwidth limit = Bandwidth.classic(capacity, refill); return Bucket4j.builder().addLimit(limit).build(); } public static String getIpAddr(ServerHttpRequest request) { String ip = ""; String str = request.getHeaders().getFirst("x-forwarded-for"); if (StringUtils.isEmpty(str)) { ip = request.getRemoteAddress().getAddress().getHostAddress(); logger.info("通过address方式限流获取到的IP为:" + ip); return ip; } else { String[] ips = str.split(","); for (String s : ips) { if (s.indexOf("192.168") != -1 || s.indexOf("172.31.202") != -1) { continue; } ip = ip + s + ","; } ip = ip.substring(0, ip.length() - 1); logger.info("通过x-forwarded-for限流获取到的IP,且过滤掉内网后的地址为:" + ip); if (ip.indexOf(",") != -1) { ip = ip.substring(0, ip.indexOf(",")); return ip.trim(); } return ip.trim(); } }}复制代码
配置route: TEST-AUTH/**的api接口都会路由到TEST-AUTH服务
@SpringBootApplicationpublic class GateWayApplication { public static void main(String[] args) { SpringApplication.run(GateWayApplication.class, args); } @Bean public RouteLocator routeLocator(RouteLocatorBuilder builder) { return builder.routes() // 认证中心 .route(r -> r.path("/TEST-AUTH/**") .filters(f -> f.stripPrefix(1).filter(new RateLimitByIpFilter(1, 1, Duration.ofSeconds(1)))) .uri("lb://TEST-AUTH").id("TEST-AUTH")) .build(); }}复制代码三、分布式限流(这里摘自京东抢购业务)
使用Redis+Lua的方式来实现
local key = "rate.limit:" .. KEYS[1] --限流KEYlocal limit = tonumber(ARGV[1]) --限流大小local current = tonumber(redis.call('get', key) or "0")if current + 1 > limit then --如果超出限流大小 return 0else --请求数+1,并设置1秒过期 redis.call("INCRBY", key,"1") redis.call("expire", key,"1") return 1end复制代码
public static boolean accquire() throws IOException, URISyntaxException { Jedis jedis = new Jedis("127.0.0.1"); File luaFile = new File(RedisLimitRateWithLUA.class.getResource("/").toURI().getPath() + "limit.lua"); String luaScript = FileUtils.readFileToString(luaFile); String key = "ip:" + System.currentTimeMillis()/1000; // 当前秒 String limit = "5"; // 最大限制 List<String> keys = new ArrayList<String>(); keys.add(key); List<String> args = new ArrayList<String>(); args.add(limit); Long result = (Long)(jedis.eval(luaScript, keys, args)); // 执行lua脚本,传入参数 return result == 1;}复制代码
简单说明一下:redis k = rate.limit:ip:当前秒 V: 5
作者:汀雨笔记
链接:
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
标签: #java中默认访问权限 #java统计访问次数 #java限制接口访问次数