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

本文介绍了如何在SpringBoot应用中利用Redis的setnx命令和自定义Idempotent注解来确保接口的幂等性。通过在方法上添加注解,结合Redis的key-value存储,确保相同请求在一定时间内只能执行一次,其余请求会被拦截。详细步骤包括引入Redis支持,创建幂等异常、幂等注解、幂等切面以及关键工具类的实现。
摘要由CSDN通过智能技术生成

1.简述

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

2.引入redis支持

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

2.1.pom.xml

org.springframework.boot

spring-boot-starter-data-redis

spring-boot-starter-logging

org.springframework.boot

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 redisTemplate;

@Test

public void simpleTest() {

ValueOperations 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 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) 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) {

//..

}

}

标签:SpringBoot,自定义,Idempotent,redis,class,key,public,String

来源: https://blog.csdn.net/hanchao5272/article/details/92073405

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值