SpringBoot中通过Redis的setnx和自定义注解@Idempotent实现API幂等处理

SpringBoot中通过Redis的setnx和自定义注解@Idempotent实现API幂等处理1 简述目的 一定时间内 同样的请求 业务参数相同 访问同一个接口 则只能成功一次 其余被拒绝 2 引入 redis 支持因为需要通过 redis 的 setnx 确保只有一个接口能够正常访问 所以需要引入 redis 2 1 pom xml dependency groupId org springframew boot groupId dependency

1.简述

  • 目的:一定时间内,同样的请求(业务参数相同)访问同一个接口,则只能成功一次,其余被拒绝。

2.引入redis支持

因为需要通过redissetnx确保只有一个接口能够正常访问,所以需要引入redis。

2.1.pom.xml

<dependency> <groupId>org.springframework.boot 
     groupId> <artifactId>spring-boot-starter-data-redis 
      artifactId> <exclusions>  
      <exclusion> <artifactId>spring-boot-starter-logging 
       artifactId> <groupId>org.springframework.boot 
        groupId>  
         exclusion>  
          exclusions>  
           dependency> 

2.2.application.properties

spring.redis.host=11.22.33.44 spring.redis.port=26379 spring.redis.database=1 spring.redis.pool.max-active=8 spring.redis.pool.max-wait=-1 spring.redis.pool.max-idle=500 spring.redis.pool.min-idle=0 spring.redis.timeout=0 

2.3.Redis JUnit Test Case

/ * @author hanchao */ @RunWith(SpringRunner.class) @SpringBootTest public class RedisTemplateTest { 
    @Resource private RedisTemplate<String,String > redisTemplate; @Test public void simpleTest() { 
    ValueOperations<String,String> valueOperations = redisTemplate.opsForValue(); String key = "RedisTemplateTest-simpleTest-001"; valueOperations.set(key,key+key); System.out.println(valueOperations.get(key)); } } 

3.引入幂等

3.1.幂等异常

/ * 用于专门处理幂等相关异常。 * @author hanchao */ public class IdempotentException extends RuntimeException { 
    public IdempotentException(String message) { 
    super(message); } @Override public String getMessage() { 
    return super.getMessage(); } } 

3.2.幂等注解

/ * 幂等注解 * @author wangchao */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Idempotent { 
    / * 幂等名称,作为redis缓存Key的一部分。 */ String value(); / * 幂等过期时间,即:在此时间段内,对API进行幂等处理。 */ long expireMillis(); } 

3.3.幂等切面

/ * 幂等切面 * @author wangchao */ @Aspect @Component @ConditionalOnClass(RedisTemplate.class) public class IdempotentAspect { 
    private static final Logger LOGGER = LoggerFactory.getLogger(IdempotentAspect.class); / * redis缓存key的模板 */ private static final String KEY_TEMPLATE = "idempotent_%s"; @Resource private RedisTemplate<String,String> redisTemplate; / * 根据实际路径进行调整 */ @Pointcut("@annotation(pers.hanchao......anno.Idempotent)") public void executeIdempotent() { 
    } @Around("executeIdempotent()") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { 
    //获取方法 Method method = ((MethodSignature) joinPoint.getSignature()).getMethod(); //获取幂等注解 Idempotent idempotent = method.getAnnotation(Idempotent.class); //根据 key前缀 + @Idempotent.value() + 方法签名 + 参数 构建缓存键值 //确保幂等处理的操作对象是:同样的 @Idempotent.value() + 方法签名 + 参数 String key = String.format(KEY_TEMPLATE, idempotent.value() + "_" + KeyUtil.generate(method, joinPoint.getArgs())); //通过setnx确保只有一个接口能够正常访问 //调用KeyUtil工具类生成key String redisRes = redisTemplate.execute((RedisCallback<String>) conn -> ((JedisCommands) conn.getNativeConnection()).set(key, key, "NX", "PX", idempotent.expireMillis())); if (Objects.equals("OK", redisRes)) { 
    return joinPoint.proceed(); } else { 
    LOGGER.debug("Idempotent hits, key=" + key); throw new IdempotentException("Idempotent hits, key=" + key); } } } 

3.4.工具类

/ * Key生成工具 * @author hanchao */ public class KeyUtil { 
    private static final Logger LOGGER = LoggerFactory.getLogger(KeyUtil.class); / * 根据{方法名 + 参数列表}和md5转换生成key */ public static String generate(Method method, Object... args) { 
    StringBuilder sb = new StringBuilder(method.toString()); for (Object arg : args) { 
    sb.append(toString(arg)); } return DigestUtils.md5Hex(sb.toString()); } private static String toString(Object object) { 
    if (object == null) { 
    return "null"; } if (object instanceof Number) { 
    return object.toString(); } //调用json工具类转换成String return JsonUtil.toJson(object); } } / * Json格式化工具 * @author hanchao */ public class JsonUtil { 
    private static final Logger LOGGER = LoggerFactory.getLogger(JsonUtil.class); private static final ObjectMapper MAPPER = new ObjectMapper(); static { 
    MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).setSerializationInclusion(Include.NON_NULL); } / * Java Object Maps To Json */ public static String toJson(Object obj) { 
    String result; if (obj == null || obj instanceof String) { 
    return (String) obj; } try { 
    result = MAPPER.writeValueAsString(obj); } catch (Exception e) { 
    LOGGER.error("Java Object Maps To Json Error !"); throw new RuntimeException("Java Object Maps To Json Error !", e); } return result; } } 

4.对接口标记幂等注解

@RestController public class DemoController { 
    @Resource private DemoService demoService; / * @Idempotent的value值随意,一般保持与接口url一致接口。 */ @Idempotent(value = "/cock/alarm", expireMillis = 1000L) @PostMapping(value = "/cock/alarm") public String demo(@RequestBody DemoPo po) { 
    //.. } } 
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/208605.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月19日 上午11:11
下一篇 2026年3月19日 上午11:11


相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号