滁州58同城网站怎么做,编程猫官网,创意极简logo,wordpress投稿vip问题场景
在开发中由于可能存在的网络波动问题导致用户重复提交#xff0c;所以自定义一个防抖注解。设计思路#xff1a;自定义注解加在接口的方法上#xff0c;注解中设置了SPEL表达式#xff0c;可以通过SPEL表达式从接口参数中提取Redis的Key#xff0c;以这个Key作为…问题场景
在开发中由于可能存在的网络波动问题导致用户重复提交所以自定义一个防抖注解。设计思路自定义注解加在接口的方法上注解中设置了SPEL表达式可以通过SPEL表达式从接口参数中提取Redis的Key以这个Key作为判断是否重复提交的依据。如果没有设置SPEL表达式的话就以当前登录用户的ID作为Key。同时在将数据设置到缓存的时候使用Lua脚本执行保证Redis命令的原子性。
代码实现
自定义注解
package com.creatar.common.annotation;import java.lang.annotation.*;/*** 防抖注解** author: 张定辉* date: 2024/6/13 上午9:43* description: 防抖注解*/
Target(ElementType.METHOD)
Retention(RetentionPolicy.RUNTIME)
Documented
public interface RepeatLock {/*** SPEL表达式,根据该表达式解析出的值作为key** return SPEL表达式解析得到的值*/String value();/*** redis前缀*/String prefix() default repeat_lock::;/*** 错误提示信息*/String message() default 请勿重复提交!;/*** 设置单位时间内禁止重复提交以秒为单位*/int unitTime() default 3;
}AOP注解处理器
package com.creatar.common.annotation.handler;import com.creatar.common.annotation.RepeatLock;
import com.creatar.exception.CustomException;
import com.creatar.util.SecurityUtil;
import com.creatar.util.SpelUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.expression.EvaluationContext;
import org.springframework.stereotype.Component;import java.util.Collections;
import java.util.Objects;/*** 防抖注解处理器** author: 张定辉* date: 2024/6/13 上午9:49* description: 防抖注解处理器*/
Aspect
RequiredArgsConstructor
Slf4j
Component
public class RepeatLockAspect {private final RedisTemplateString, String redisTemplate;Before(annotation(repeatLock))public void before(JoinPoint joinPoint, RepeatLock repeatLock) {String redisPrefix repeatLock.prefix();String errorMessage repeatLock.message();String value repeatLock.value();int unitTime repeatLock.unitTime();MethodSignature signature (MethodSignature) joinPoint.getSignature();EvaluationContext context SpelUtil.getContext(joinPoint.getArgs(), signature.getMethod());String key SecurityUtil.getCurrentUserId();try {key key SpelUtil.getValue(context, value, String.class);} catch (Exception e) {log.error(防抖注解获取SPEL表达式失败,类名称:{},方法名称{}\n, joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName(), e);}String redisKey redisPrefix key;//如果是重复提交则抛出异常if (isRepeat(redisKey,unitTime)) {throw new CustomException(errorMessage);}}/*** 使用Lua脚本执行原子性的Redis操作避免由于并发过大从而导致的key永久有效* 如果key不存在则设置value为1并且设置过期时间** return 如果没有key则false如果有key则返回true表示重复提交*/private boolean isRepeat(String key,int unitTime) {String scriptStr if redis.call(exists, KEYS[1]) 0 thenredis.call(set, KEYS[1], 1, ex,%s)return falseelsereturn trueend.formatted(unitTime);RedisScriptBoolean script new DefaultRedisScript(scriptStr, Boolean.class);Boolean result redisTemplate.execute(script, Collections.singletonList(key));if (Objects.isNull(result)) {return true;}return result;}
}应用
package com.creatar.controller;import com.creatar.common.Res;
import com.creatar.common.annotation.RepeatLock;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.security.PermitAll;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;/*** 测试接口** author: 张定辉* date: 2024/6/15 下午4:36* description: 测试接口*/
RestController
RequestMapping(/test)
Tag(name 测试接口,description 测试接口)
PermitAll
public class TestController {GetMapping(/testLimit)RepeatLock(value #param,unitTime 5)public ResString testLimit(RequestParam(value param)String param) {return Res.success(param);}
}