当前位置:网站首页>Two common schemes for handling interface idempotence

Two common schemes for handling interface idempotence

2022-06-21 12:50:00 _ A little rain in Jiangnan

Released last week TienChin In project video , I have combed six idempotent solutions with you , Interface idempotency processing is a very common requirement , In fact, we will encounter in many projects . Today, let's take a look at two simple implementation ideas .

1. Interface idempotency implementation scheme sorting

In fact, there are many implementation schemes for interface idempotence , Here I share two common solutions with my friends .

1.1 be based on Token

be based on Token The implementation idea of this scheme is very simple , The whole process is divided into two steps :

  1. The client sends the request , Get a from the server Token token , Each request gets a brand new token .
  2. When the client sends the request , Carry the token of the first step , Before processing the request , First verify whether the token exists , When the request is processed successfully , Just delete the token .

The general idea is like this , Of course, the specific implementation will be much more complicated , There are a lot of details to pay attention to , SongGe has recorded videos of this scheme before , Friends can refer to , Recorded two videos , One is based on interceptor processing , The other is based on AOP Faceted :

Based on interceptor processing ( Video 1 ):

be based on AOP Section treatment ( Video 2 ):

1.2 Verify based on request parameters

Recently TienChin Another solution is used in the project , This scheme is based on the request parameters , If in a short time , The request parameters received by the same interface are the same , Then consider this a duplicate request , Refuse to deal with , Basically, this is the idea .

Compared with the first scheme , The second scheme is relatively easy , Because there is only one request , There is no need to go to the server to get the token . In the high concurrency environment, this scheme has obvious advantages .

So today I'm going to talk about the implementation of the second scheme , There is TienChin The project video will also tell you in detail .

2. Verification based on request parameters

First, let's create a new one Spring Boot project , introduce Web and Redis rely on , After new creation , Let's configure Redis Basic information of , as follows :

spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=123

For the follow-up Redis It is easy to operate , Let's do it again Redis Do a simple encapsulation , as follows :

@Component
public class RedisCache {
    
    @Autowired
    public RedisTemplate redisTemplate;
    public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) {
    
        redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
    }
    public <T> T getCacheObject(final String key) {
    
        ValueOperations<String, T> operation = redisTemplate.opsForValue();
        return operation.get(key);
    }
}

This is simpler , A store of data , A read data .

Next, we customize an annotation , On the interface that needs idempotency processing , Add this comment to , In the future, this interface will automatically handle idempotence .

@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatSubmit {
    
    /** *  Time interval between (ms), Less than this time is considered as repeated submission  */
    public int interval() default 5000;

    /** *  Prompt message  */
    public String message() default " Duplicate submission is not allowed , Please try again later ";
}

This annotation is parsed by interceptors , The parsing code is as follows :

public abstract class RepeatSubmitInterceptor implements HandlerInterceptor {
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
        if (handler instanceof HandlerMethod) {
    
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            Method method = handlerMethod.getMethod();
            RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);
            if (annotation != null) {
    
                if (this.isRepeatSubmit(request, annotation)) {
    
                    Map<String, Object> map = new HashMap<>();
                    map.put("status", 500);
                    map.put("msg", annotation.message());
                    response.setContentType("application/json;charset=utf-8");
                    response.getWriter().write(new ObjectMapper().writeValueAsString(map));
                    return false;
                }
            }
            return true;
        } else {
    
            return true;
        }
    }

    /** *  Verify whether to submit repeatedly. Subclasses implement specific rules to prevent repeated submission  * * @param request * @return * @throws Exception */
    public abstract boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation);
}

The interceptor is an abstract class , Intercept the interface method , Then find the... On the interface @RepeatSubmit annotation , call isRepeatSubmit Method to determine whether the data is repeatedly submitted , This method is an abstract method here , We need to define another class that inherits from this abstract class , In the new subclass , There can be different idempotency judgment logic , Here we are based on URL Address + Parameters To determine whether the idempotency condition is satisfied :

@Component
public class SameUrlDataInterceptor extends RepeatSubmitInterceptor {
    
    public final String REPEAT_PARAMS = "repeatParams";

    public final String REPEAT_TIME = "repeatTime";
    public final static String REPEAT_SUBMIT_KEY = "REPEAT_SUBMIT_KEY";

    private String header = "Authorization";

    @Autowired
    private RedisCache redisCache;

    @SuppressWarnings("unchecked")
    @Override
    public boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation) {
    
        String nowParams = "";
        if (request instanceof RepeatedlyRequestWrapper) {
    
            RepeatedlyRequestWrapper repeatedlyRequest = (RepeatedlyRequestWrapper) request;
            try {
    
                nowParams = repeatedlyRequest.getReader().readLine();
            } catch (IOException e) {
    
                e.printStackTrace();
            }
        }

        // body The parameter is empty. , obtain Parameter The data of 
        if (StringUtils.isEmpty(nowParams)) {
    
            try {
    
                nowParams = new ObjectMapper().writeValueAsString(request.getParameterMap());
            } catch (JsonProcessingException e) {
    
                e.printStackTrace();
            }
        }
        Map<String, Object> nowDataMap = new HashMap<String, Object>();
        nowDataMap.put(REPEAT_PARAMS, nowParams);
        nowDataMap.put(REPEAT_TIME, System.currentTimeMillis());

        //  Request address ( As storage cache Of key value )
        String url = request.getRequestURI();

        //  The only value ( If there is no header, use the request address )
        String submitKey = request.getHeader(header);

        //  Unique identification ( Appoint key + url +  The message header )
        String cacheRepeatKey = REPEAT_SUBMIT_KEY + url + submitKey;

        Object sessionObj = redisCache.getCacheObject(cacheRepeatKey);
        if (sessionObj != null) {
    
            Map<String, Object> sessionMap = (Map<String, Object>) sessionObj;
            if (compareParams(nowDataMap, sessionMap) && compareTime(nowDataMap, sessionMap, annotation.interval())) {
    
                return true;
            }
        }
        redisCache.setCacheObject(cacheRepeatKey, nowDataMap, annotation.interval(), TimeUnit.MILLISECONDS);
        return false;
    }

    /** *  Judge whether the parameters are the same  */
    private boolean compareParams(Map<String, Object> nowMap, Map<String, Object> preMap) {
    
        String nowParams = (String) nowMap.get(REPEAT_PARAMS);
        String preParams = (String) preMap.get(REPEAT_PARAMS);
        return nowParams.equals(preParams);
    }

    /** *  Judge the interval between two times  */
    private boolean compareTime(Map<String, Object> nowMap, Map<String, Object> preMap, int interval) {
    
        long time1 = (Long) nowMap.get(REPEAT_TIME);
        long time2 = (Long) preMap.get(REPEAT_TIME);
        if ((time1 - time2) < interval) {
    
            return true;
        }
        return false;
    }
}

Let's look at the specific implementation logic :

  1. First, judge whether the current request object is RepeatedlyRequestWrapper, If it is , Indicates that the current request parameter is JSON, Then pass IO The stream reads the parameters , This little partner should understand it in combination with the previous article , Otherwise, you may feel confused , Portal JSON Once the data is read, it is gone , What do I do ?.
  2. If in the first step , I didn't get the parameters , Then the description parameter may not be JSON Format , It is key-value Format , Then we should key-value To read out the parameters , And turn it into a JSON character string .
  3. Next, construct a Map, Save the previously read parameters and the current time to Map in .
  4. Next, the structure is saved to Redis Of the data in key, This key Fixed prefix by + request URL Address + The authentication token of the request header consists of , The token of this request header is still very important and necessary , Only in this way can the data submitted by the current user be distinguished ( If it is RESTful Style interface , So in order to distinguish , You can also splice the request method of the interface as a parameter to key in ).
  5. Next go to Redis Get data in , After acquisition , Compare whether the parameters are the same and whether the time has expired .
  6. If the judgment is all right , return true, Indicates that the request is repeated .
  7. Otherwise, the return indicates that this is the first time the user has submitted data to this interface or that the time window has passed , Then cache the parameter string back to Redis in , And back to false, The request is OK .

All right. , Finish it all , Finally, let's configure the interceptor :

@Configuration
public class WebConfig implements WebMvcConfigurer {
    

    @Autowired
    RepeatSubmitInterceptor repeatSubmitInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    
        registry.addInterceptor(repeatSubmitInterceptor)
                .addPathPatterns("/**");
    }
}

such , The idempotency of our interface is handled ~ When needed , You can use it directly on the interface :

@RestController
public class HelloController {
    

    @PostMapping("/hello")
    @RepeatSubmit(interval = 100000)
    public String hello(@RequestBody String msg) {
    
        System.out.println("msg = " + msg);
        return "hello";
    }
}

All right. , Official account back office reply RepeatSubmit You can download the source code of this article .

原网站

版权声明
本文为[_ A little rain in Jiangnan]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/172/202206211233005142.html