1.通过aop 环绕通知,实现后端对前端的同一请求的连续点击操作拦截。
2.实现步骤:
2.1 自定义注解类
/** * 防误触 */ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface DistinctRequest { /** * 名称 * @return */ String name() default ""; /** * 几秒后可再次请求 * @return */ int expires() default 10; } |
2.1 自定义切面类
import cn.hutool.crypto.digest.MD5; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.serializer.SerializerFeature; import com.github.benmanes.caffeine.cache.AsyncCache; import com.github.benmanes.caffeine.cache.Caffeine; import com.me.test.config.annotation.DistinctRequest; import lombok.extern.slf4j.Slf4j; 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.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; @Slf4j @Component @Aspect public class DistinctRequestAspect { /** * 单台服务可以用Caffeine缓存,多台可用redi缓存 * Caffeine 不对灵活使用过期时间不友好 * redis 可以自定义过期时间,与代码产生强依赖 */ public static AsyncCache<String, String> loadingCache = Caffeine.newBuilder() .expireAfterWrite(5, TimeUnit.SECONDS) .maximumSize(10_000) .buildAsync(); public static Map<String, String> map = new HashMap<>(); /** * 以 controller 包下定义的所有请求为切入点 */ @Pointcut("@annotation(com.me.test.config.annotation.DistinctRequest)") public void webLogs() { } @Around("webLogs() && @annotation(distinctRequest)") public Object doAround(ProceedingJoinPoint joinPoint, DistinctRequest distinctRequest) throws Throwable { // 开始打印请求日志 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); /**可通过对ip+路径进行hash 校验*/ // String ip = request.getHeader("x-forwarded-for"); /**可通过对用户信息+路径进行hash 校验*/ // String userInf = request.getHeader("token"); if (!request.getMethod().equalsIgnoreCase("post")) { log.info("URL : {}", request.getServletPath() + ", data :" + JSON.toJSONString(joinPoint.getArgs()[0])); return joinPoint.proceed(); } String jsonStr = JSON.toJSONString(joinPoint.getArgs()[0], SerializerFeature.MapSortField, SerializerFeature.PrettyFormat); String hash = MD5.create().digestHex(jsonStr); if (loadingCache.getIfPresent(hash) != null) { System.out.println(); throw new Exception("请等5秒钟再试"); } else { // Insert or update an entry loadingCache.put(hash, loadingCache.get(hash, k -> hash)); // loadingCache.synchronous().invalidate(hash); } // 打印请求 url // log.info("URL : {}", request.getServletPath()); return joinPoint.proceed(); } |
2.1 控制层
@PostMapping("/crate") @DistinctRequest(expires = 5) public String crate(@RequestBody DTO dto) { } |