1.简述
- 目的:一定时间内,同样的请求(业务参数相同)访问同一个接口,则只能成功一次,其余被拒绝。
2.引入redis支持
因为需要通过redis的setnx确保只有一个接口能够正常访问,所以需要引入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
