SpringBoot中通过Redis的setnx和自定义注解@Idempotent实现API幂等处理「建议收藏」

SpringBoot中通过Redis的setnx和自定义注解@Idempotent实现API幂等处理「建议收藏」1.简述目的:一定时间内,同样的请求(业务参数相同)访问同一个接口,则只能成功一次,其余被拒绝。2.引入redis支持因为需要通过redis的setnx确保只有一个接口能够正常访问,所以需要引入redis。2.1.pom.xml<dependency><groupId>org.springframework.boot</groupId>…_1671465600

大家好,欢迎来到IT知识分享网。

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>

IT知识分享网

2.2.application.properties

IT知识分享网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.幂等异常

IT知识分享网/** * 用于专门处理幂等相关异常。 * @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://yundeesoft.com/7588.html

(0)
上一篇 2023-01-03 09:55
下一篇 2023-01-05 17:43

相关推荐

发表回复

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

关注微信