API接口 - 限制重复调用(防刷)
- 环境
- 代码实现
- 实现方式
- 定义注解
- 注解应用
环境
JDK 8
SpringBoot
代码实现
实现方式
定义注解,通过切面的方式,检测重复调用。
定义注解
import cn.nhd.fsl.entity.system.Prevent;
import cn.nhd.fsl.enums.PreventStrategyEnums;
import cn.nhd.fsl.exception.FslServiceException;
import com.alibaba.fastjson.JSON;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
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.util.StringUtils;import java.lang.reflect.Method;
import java.util.Base64;
import java.util.concurrent.TimeUnit;/*** 防刷切面实现类*/
@Aspect
@Component
public class PreventAop {private static Logger log = LoggerFactory.getLogger(PreventAop.class);@Autowiredprivate RedisTemplate<String, String> redisTemplate;private final String redisKey = "fsl:system:resubmit:";/*** 切入点*/@Pointcut("@annotation(cn.nhd.fsl.entity.system.Prevent)")public void pointcut() {}/*** 处理前** @return*/@Before("pointcut()")public void joinPoint(JoinPoint joinPoint) throws Exception {String requestStr = JSON.toJSONString(joinPoint.getArgs()[0]);if (StringUtils.isEmpty(requestStr) || requestStr.equalsIgnoreCase("{}")) {throw new FslServiceException("[防刷]入参不允许为空");}MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();Method method = joinPoint.getTarget().getClass().getMethod(methodSignature.getName(),methodSignature.getParameterTypes());Prevent preventAnnotation = method.getAnnotation(Prevent.class);String methodFullName = method.getDeclaringClass().getName() + method.getName();entrance(preventAnnotation, requestStr,methodFullName);return;}/*** 入口** @param prevent* @param requestStr*/private void entrance(Prevent prevent, String requestStr,String methodFullName) throws Exception {PreventStrategyEnums strategy = prevent.strategy();switch (strategy) {case DEFAULT:defaultHandle(requestStr, prevent,methodFullName);break;default:throw new FslServiceException("无效的策略");}}/*** 默认处理方式** @param requestStr* @param prevent*/private void defaultHandle(String requestStr, Prevent prevent,String methodFullName) throws Exception {String base64Str = toBase64String(requestStr);long expire = Long.parseLong(prevent.value());String resp = redisTemplate.opsForValue().get(redisKey+methodFullName+base64Str);if (StringUtils.isEmpty(resp)) {redisTemplate.opsForValue().set(redisKey+methodFullName+base64Str, requestStr, expire, TimeUnit.SECONDS);} else {String message = !StringUtils.isEmpty(prevent.message()) ? prevent.message() :expire + "秒内不允许重复请求";throw new FslServiceException(message);}}/*** 对象转换为base64字符串** @param obj 对象值* @return base64字符串*/private String toBase64String(String obj) throws Exception {if (StringUtils.isEmpty(obj)) {return null;}Base64.Encoder encoder = Base64.getEncoder();byte[] bytes = obj.getBytes("UTF-8");return encoder.encodeToString(bytes);}
}
import cn.nhd.fsl.enums.PreventStrategyEnums;import java.lang.annotation.*;/*** 接口防刷注解* 使用:* 在相应需要防刷的方法上加上* 该注解,即可** @author: min.wang*/
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Prevent {/*** 限制的时间值(秒)** @return*/String value() default "60";/*** 提示*/String message() default "";/*** 策略** @return*/PreventStrategyEnums strategy() default PreventStrategyEnums.DEFAULT;
}
注解应用
@Prevent(message = "10秒内不允许重复调多次", value = "10")