Gateway IP接口防抖操作

今天遇到一个小坑,公司要求前端做页面缓存,但是缓存之后会造成一个问题,那就是有时候新增会发生重复请求,甚至重复请求5次,虽然前端已经做了防抖操作,但是这种系统层面的防抖还是做不到的,所以后端也必须做接口防抖。

参考网上的解决方案,大多都是通过写注解+过滤器来实现对接口防抖,但是这种方案需要在每个方法或者每个类上面写注解,未免也太过繁杂了,于是想着可不可以在gateway层面进行IP检测+接口防抖?说干就干!

首先在Gateway中注册一个全局过滤器,优先级放在最前面:

@Component
public class RequestLimitFilter implements GlobalFilter, Ordered {
    private static final Logger log = LoggerFactory.getLogger(RequestLimitFilter.class);



    @Autowired
    private RedisService redisService;


    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpRequest.Builder mutate = request.mutate();

        return chain.filter(exchange.mutate().request(mutate.build()).build());
    }
}

接着我们需要写一下获取用户真实IP地址的方法:

 private static final String IP_UNKNOWN = "unknown";
    private static final String IP_LOCAL = "127.0.0.1";
    private static final int IP_LEN = 15;

    /**
     * 获取客户端真实ip
     * @param request request
     * @return 返回ip
     */
    public static String getIP(ServerHttpRequest request) {
        HttpHeaders headers = request.getHeaders();
        String ipAddress = headers.getFirst("x-forwarded-for");
        if (ipAddress == null || ipAddress.length() == 0 || IP_UNKNOWN.equalsIgnoreCase(ipAddress)) {
            ipAddress = headers.getFirst("Proxy-Client-IP");
        }
        if (ipAddress == null || ipAddress.length() == 0 || IP_UNKNOWN.equalsIgnoreCase(ipAddress)) {
            ipAddress = headers.getFirst("WL-Proxy-Client-IP");
        }
        if (ipAddress == null || ipAddress.length() == 0 || IP_UNKNOWN.equalsIgnoreCase(ipAddress)) {
            ipAddress = Optional.ofNullable(request.getRemoteAddress())
                    .map(address -> address.getAddress().getHostAddress())
                    .orElse("");
            if (IP_LOCAL.equals(ipAddress)) {
                // 根据网卡取本机配置的IP
                try {
                    InetAddress inet = InetAddress.getLocalHost();
                    ipAddress = inet.getHostAddress();
                } catch (UnknownHostException e) {
                    // ignore
                }
            }
        }

        // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
        if (ipAddress != null && ipAddress.length() > IP_LEN) {
            int index = ipAddress.indexOf(",");
            if (index > 0) {
                ipAddress = ipAddress.substring(0, index);
            }
        }
        return ipAddress;
    }

这个时候还没完,这个方法需要nginx来支持,否则是获取不到用户真实IP的,附nginx配置:

location / {
    add_header Cache-Control no-cache;
    root   html;
    index  index.html index.htm;
    try_files $uri $uri/ /index.html;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

接下来我们就用redis进行记录用户访问操作,实现一秒内用一个IP只可访问一次接口:

@Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpRequest.Builder mutate = request.mutate();

        String url = request.getURI().getPath();
        String ip = IpUtils.getIP(request);
        String key = ip+ "_" + url;
        //使用请求方式判断
//        String method = request.getMethodValue();
//        if(StrUtil.isNotBlank(method) || method.equals(HttpMethod.POST.name())){
//            String value = (String) redisService.getCacheObject(key);
//            if(StrUtil.isBlank(value)){
//                redisService.setCacheObject(key,"try access",1L, TimeUnit.SECONDS);
//            }else {
//                return unauthorizedResponse(ip,exchange, "1秒内不可重复请求");
//            }
//        }
        //使用请求路径判断
        if(url.contains("save") || url.contains("Save") || url.contains("add") || url.contains("Add") || url.contains("insert") || url.contains("Insert")){
            String value = (String) redisService.getCacheObject(key);
            if(StrUtil.isBlank(value)){
                redisService.setCacheObject(key,"try access",1L, TimeUnit.SECONDS);
            }else {
                return unauthorizedResponse(ip,exchange, "1秒内不可重复请求");
            }
        }
        return chain.filter(exchange.mutate().request(mutate.build()).build());
    }

测试一下:

这样我们就实现了IP+接口防抖操作,还是挺新鲜的

完整代码在这里:

/**
 * 同一个IP访问接口限制
 *
 * @author ruoyi
 */
@Component
public class RequestLimitFilter implements GlobalFilter, Ordered {
    private static final Logger log = LoggerFactory.getLogger(RequestLimitFilter.class);



    @Autowired
    private RedisService redisService;


    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpRequest.Builder mutate = request.mutate();

        String url = request.getURI().getPath();
        String ip = IpUtils.getIP(request);
        String key = ip+ "_" + url;
        //使用请求方式判断
//        String method = request.getMethodValue();
//        if(StrUtil.isNotBlank(method) || method.equals(HttpMethod.POST.name())){
//            String value = (String) redisService.getCacheObject(key);
//            if(StrUtil.isBlank(value)){
//                redisService.setCacheObject(key,"try access",1L, TimeUnit.SECONDS);
//            }else {
//                return unauthorizedResponse(ip,exchange, "1秒内不可重复请求");
//            }
//        }
        //使用请求路径判断
        if(url.contains("save") || url.contains("Save") || url.contains("add") || url.contains("Add") || url.contains("insert") || url.contains("Insert")){
            String value = (String) redisService.getCacheObject(key);
            if(StrUtil.isBlank(value)){
                redisService.setCacheObject(key,"try access",1L, TimeUnit.SECONDS);
            }else {
                return unauthorizedResponse(ip,exchange, "1秒内不可重复请求");
            }
        }
        return chain.filter(exchange.mutate().request(mutate.build()).build());
    }


    private Mono<Void> unauthorizedResponse(String ip,ServerWebExchange exchange, String msg) {
        log.error("{}请求重复:{}",ip, exchange.getRequest().getPath());
        return ServletUtils.webFluxResponseWriter(exchange.getResponse(), msg, HttpStatus.BAD_REQUEST);
    }

    @Override
    public int getOrder() {
        return -300;
    }
}

欢迎大家关注我的公众号:Java技术与面经,所有的产品创作过程都会在这上面展示的哦 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值