springcloud -自定义注解+redis+spring aop 实现接口限流防刷

【接口防刷机制】

 

       主要防止短时间接口被大量调用(攻击),出现系统崩溃和系统爬虫问题,提升服务的可用性。限制同一用户一定时间内(如1 min)只能访问固定次数,可以减少对业务的侵入,在服务端对系统做一层保护.

 

【实现方案】

        本文主要是通过 自定义注解+redis+spring aop+全局异常的方式实现接口限流防刷功能。

  • 自定义注解
import java.lang.annotation.*;



@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface RequestLimit {

    /**
     * 调用的次数
     * @return
     */
    int count() default 3;

    /**
     * 时间段; 在time内调用的次数count -单位秒
     * @return
     */
    int frameTime() default 1;

    /**
     * 锁定时间 -单位小时
     * @return
     */
    int  lockTime() default  1;

}

 

  • spring AOP切面
@Slf4j
@Aspect
@Component
public class RequestLimitContract {

    @Autowired
    private TblUserService userService;

    @Autowired
    private TblBlackListService tblBlackListService;

    @Autowired
    private RedisClient redisClient;


    //region 环绕通知
    @Around("@annotation(limit)")
    @Transactional(rollbackFor = Exception.class)
    public Object requestLimit(ProceedingJoinPoint process, RequestLimit limit) {

        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        long startTime = System.currentTimeMillis();

        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        MethodSignature methodSignature = (MethodSignature) process.getSignature();
        Method method = methodSignature.getMethod();
        String methodName = method.getDeclaringClass().getName() + "." + method.getName();
        // 根据 IP + API 限流
        String requestURI = request.getRequestURI();
        String className = method.getDeclaringClass().getName();
        String uid = request.getHeader("uid");

        Object[] args = process.getArgs();
        if (StringUtils.isEmpty(uid)) {
            uid = getUser (args[0], requestURI);
        }
        log.info("请求时间:{}, clientIp:{}, 请求方法:{}, 请求参数{}", simpleDateFormat.format(startTime), "", methodName);

        // result是方法的最终返回结果
        Object result = null;
        try {
            //  查询ip是否被锁定 -是 直接返回 -否 继续流程
            String ipAddress = getIpAddr(request);
            RedisVo redisVo = new RedisVo();
            redisVo.setKey(ipAddress);
            GenericResponse getTime = redisClient.getExpire(redisVo);
            log.error("-------------redis过期时间-------------" + getTime);
            if (getTime.getCode().equals(CommonEnum.ResponseCode.Success.getCode())) {
                // 获取key的过期时间
                String time = getTime.getResult().toString();
                long intValue = Double.valueOf(time).longValue();
                if (intValue >= limit.frameTime()) {
                    return new GenericResponse(CommonEnum.ResponseCode.操作过于频繁.getCode(), "已经放入小黑屋,封锁一个小时");
                }
            }
            // TODO 检查用户id (或者ip)是否在黑名单中
            boolean b = checkBlackList(uid);
            if (b) {
                return new GenericResponse(CommonEnum.ResponseCode.操作过于频繁.getCode(), "用户操作异常");
            }
            redisVo.setKey(requestURI + ipAddress);
            GenericResponse getRedisCodeResponse = redisClient.get(redisVo);
            Integer maxTimes = 0;
            if (StringUtils.isNotEmpty(getRedisCodeResponse.getResult().toString())) {
                maxTimes = Integer.parseInt(getRedisCodeResponse.getResult().toString());
            }
            if (maxTimes.equals(0)) {
                //set时一定要加过期时间
                redisVo.setTime(limit.frameTime());
                redisVo.setValue("1");
                redisVo.setTimeUnit(TimeUnit.SECONDS);
                redisClient.setByTime(redisVo);
                log.error("-------------用户正常-------------");
                //调用执行目标方法
                result = process.proceed();
            } else if (maxTimes < limit.count()) {
                redisVo.setValue(String.valueOf(maxTimes + 1));
                redisClient.incr(redisVo);
                log.error("------------请求频繁--------------");
                //调用执行目标方法
                result = process.proceed();
            } else {
                // TODO 将该ip对应用户UID 设备号插入黑名单中,并将上下级用户插入观察者表中
                if (!StringUtils.isEmpty(uid)&&!"0".equals(uid.trim())) {
                    tblBlackListService.insertBlack(uid, ipAddress, requestURI);
                }
                // 访问太频繁,加入到redis中锁定一个小时
                log.error("------------ 访问太频繁,ip加入到redis中锁定一个小时--------------");
                redisVo.setKey(ipAddress);
                redisVo.setTime(limit.lockTime());
                redisVo.setValue("1");
                redisVo.setTimeUnit(TimeUnit.HOURS);
                redisClient.setByTime(redisVo);
                return new GenericResponse(CommonEnum.ResponseCode.操作过于频繁.getCode(), "操作过于频繁");

            }

        } catch (Throwable throwable) {
            String exception = throwable.getClass() + ":" + throwable.getMessage();
            long costTime = System.currentTimeMillis() - startTime;
            log.error("请求时间:{}, 请求耗时:{}, 请求类名:{}, 请求方法:{}, 请求参数:{}, 请求结果:{}", startTime, costTime, className, methodName, "", exception);
            return new GenericResponse(CommonEnum.ResponseCode.Fail.getCode(), "服务器异常", exception);
        }
        long costTime = System.currentTimeMillis() - startTime;
        log.info("请求时间:{}, 请求耗时:{}, 请求类名:{}, 请求方法:{}, 请求参数:{}, 请求结果:{}", simpleDateFormat.format(startTime), costTime, className, methodName, "", new Gson().toJson(result));
        return result;
    }

    //  查询用户是否在黑名单中
    private boolean checkBlackList(String uid) {
        TblBlackList tblBlackList = new TblBlackList();
        tblBlackList.setUid(uid);
        tblBlackList = tblBlackListService.getTblBlackListBy(tblBlackList);
        if (tblBlackList != null) {
            return true;
        } else {
            return false;
        }
    }

 

    //获取用户id
    private String getUser (Object result, String methodName) {
        if (methodName.equals("/user/sendLoginVerificationCode")) {
            // 请求参数中包含手机号
            String userTelephone = (String) JSONObject.fromObject(result).get("telephone");
            TblUser tblUser = userService.getEntityByPhoneTest(userTelephone);
            if (tblUser != null) {
                return tblUser.getId().toString();
            }

        } else if (methodName.equals("/task/invite/bindPhone")) {
            String uid = (JSONObject.fromObject(result).get("uid")).toString();
            return uid;
        } else if (methodName.equals("/sign/day/sinIn")) {
            String uid = (JSONObject.fromObject(result).get("uid")).toString();
            return uid;
        } else {
            String uid = (JSONObject.fromObject(result).get("uid")).toString();
            return uid;
        }
        return "";
    }
}

 

只需要限流的接口上加上注解即可:
@RequestLimit(count = 3, frameTime = 60, lockTime = 1)
 

【流程图】

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值