使用注解,对特定接口进行重复数据的校验
@RepeatSubmit
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;
/**
* @Author
* @Description
* @Date create in 2023-12-26 11:21
*/
@Retention(RetentionPolicy.RUNTIME)//运行时生效
@Target(ElementType.METHOD)//作用在方法上
public @interface RepeatSubmit {
/**
* key的前缀,默认取方法全限定名,除非我们在不同方法上对同一个资源做频控,就自己指定
*
* @return key的前缀
*/
String prefixKey() default "";
/**
* 频控对象,默认el表达指定具体的频控对象
* 对于ip 和uid模式,需要是http入口的对象,保证RequestHolder里有值
*
* @return 对象
*/
Target target() default Target.EL;
/**
* springEl 表达式,target=EL必填
*
* @return 表达式
*/
String spEl() default "";
/**
* 频控时间范围,默认单位秒
*
* @return 时间范围
*/
int time();
/**
* 频控时间单位,默认秒
*
* @return 单位
*/
TimeUnit unit() default TimeUnit.SECONDS;
enum Target {
UID, IP, EL
}
}
aspect
import cn.hutool.core.util.StrUtil;
import cn.tcent.hr.recruit.core.annotation.RepeatSubmit;
import cn.tcent.hr.recruit.core.exception.DataCheckException;
import cn.tcent.hr.recruit.core.utils.RedisUtil;
import cn.tcent.hr.recruit.core.utils.RequestHolder;
import cn.tcent.hr.recruit.core.utils.SpElUtils;
import cn.tcent.hr.recruit.enums.ErrorCode;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
/**
* @Author
* @Description
* @Date create in 2023-12-26 11:22
*/
@Aspect
@Component
public class RepeatSubmitAspect {
@Pointcut("@annotation(cn.tcent.hr.recruit.core.annotation.RepeatSubmit)")
public void authorityPointCut() {
}
@Around("authorityPointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
RepeatSubmit authentication = method.getAnnotation(RepeatSubmit.class);
String key = "";
if (authentication != null) {
String prefix = StrUtil.isBlank(authentication.prefixKey()) ? SpElUtils.getMethodKey(method) + ":index:" : authentication.prefixKey();//默认方法限定名+注解排名(可能多个)
switch (authentication.target()) {
case EL:
key = prefix + ":" + SpElUtils.parseSpEl(method, joinPoint.getArgs(), authentication.spEl());
break;
case IP:
key = prefix + ":" + RequestHolder.get().getIp();
break;
case UID:
key = prefix + ":" + RequestHolder.get().getUid().toString();
}
}
if (StringUtils.isEmpty(key)) {
return joinPoint.proceed();
}
assert authentication != null;
int time = authentication.time();
TimeUnit unit = authentication.unit();
Boolean setnx = RedisUtil.setnx(key, unit.toSeconds(time), "1");
if (!setnx) {
throw new DataCheckException(ErrorCode.REPETITIVE_OPERATION.returnCode(), ErrorCode.REPETITIVE_OPERATION.returnDesc());
}else {
try {
return joinPoint.proceed();
} finally {
boolean delete = RedisUtil.delete(key);
}
}
}
}
SpElUtils
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.lang.reflect.Method;
import java.util.Optional;
/**
* Description: spring el表达式解析
* Author: <a href="https://github.com/zongzibinbin">abin</a>
* Date: 2023-04-22
*/
public class SpElUtils {
private static final ExpressionParser parser = new SpelExpressionParser();
private static final DefaultParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
public static String parseSpEl(Method method, Object[] args, String spEl) {
String[] params = Optional.ofNullable(parameterNameDiscoverer.getParameterNames(method)).orElse(new String[]{});//解析参数名
EvaluationContext context = new StandardEvaluationContext();//el解析需要的上下文对象
for (int i = 0; i < params.length; i++) {
context.setVariable(params[i], args[i]);//所有参数都作为原材料扔进去
}
String[] split = spEl.split(",");
StringBuilder str = new StringBuilder();
for (String s : split) {
Expression expression = parser.parseExpression(s);
str.append(expression.getValue(context, String.class));
}
return str.toString();
}
public static String getMethodKey(Method method) {
return method.getDeclaringClass() + "#" + method.getName();
}
}
使用按钮
@PostMapping(value = "test-a")
@ApiOperation(value = "推荐保存接口", httpMethod = "POST")
@RepeatSubmit(prefixKey="test-a",time=1,unit = TimeUnit.MINUTES,spEl = "#request.applicantName,#request.applicantMail,#request.applicantMobile")
public ApiResult apply(@RequestBody RecommendRequest request) {
return new ApiResult<>(ErrorCode.SUCCESS.returnCode(), "推荐成功");
}
// 或者
@PostMapping(value = "test-b")
@RepeatSubmit(prefixKey="signAddress",time=10,spEl = "#signCode,#mobile")
public ApiResult<String> getSignAddress(@RequestParam("signCode") String signCode, @RequestParam("mobile") String mobile) {
ApiResult result;
return result;
}