In a real development project, an exposed interface is often faced with a sudden number of repeated requests. If you want to filter out repeated requests that cause harm to the business, you need to implement idempotent!

Let’s explain the concept of idempotent:

Any number of executions will have the same impact as one execution. By this definition, the ultimate implication is that the impact on the database must be one-time and not repeated.

How to ensure its idempotent, usually have the following means:

1, the database to establish a unique index, can ensure that the final insert database only one data

2. Token mechanism: The interface obtains a token before each request, and then adds the token to the header body of the request in the next request for background verification. If the verification succeeds, the token is determined again in the next request

3, pessimistic lock or optimistic lock, pessimistic lock can ensure that every time for update, other SQL cannot update data (innoDB engine,select condition must be unique index, prevent full table lock)

4, the first query after judgment, first by querying the database whether there is data, if there is proof has requested, directly reject the request, if there is no proof, it is the first time to come in, direct release.

Redis implements automatic idempotent:

Build Redis service Api

1. First, set up redis server.

2, the introduction of Springboot to redis stater, or Spring packaged Jedis can also be used, the main API used in the following is its set method and exists method, here we use springboot packaged redisTemplate

@componentPublic class RedisService {@autoWired private RedisTemplate RedisTemplate; /** * public Boolean set(final String key, Object value) { boolean result = false; try { ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue(); operations.set(key, value); result = true; } catch (Exception e) { e.printStackTrace(); } return result; } /** * public Boolean setEx(final String key, Object value, Long expireTime) { boolean result = false; try { ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue(); operations.set(key, value); redisTemplate.expire(key, expireTime, TimeUnit.SECONDS); result = true; } catch (Exception e) { e.printStackTrace(); } return result; } /** * Public Boolean exists(final String key) {return redisTemplate.hasKey(key); } /** * public Object get(final String key) {Object result = null; ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue(); result = operations.get(key); return result; } /** * delete value * @param key */ public Boolean remove(final String key) {if (exists(key)) {Boolean delete = redisTemplate.delete(key); return delete; } return false; }}Copy the code

Custom annotation AutoIdempotent

Define a custom annotation. The main purpose of defining this annotation is to add it to methods that need to implement idempotent. Any method that annotates it will implement automatic idempotent.

If the annotation is scanned by reflection in the background, the METHOD is automatically idempotent, using the meta annotation elementType. METHOD to indicate that it can only be placed on the METHOD, and etentionPolicy.runtime to indicate that it is at RUNTIME.

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoIdempotent {
}Copy the code

Token creation and verification

1. Token service interface

We create a new interface to create the token service, which is mainly two methods, one for creating the token, one for verifying the token.

Creating a token is a string. Checking a token is a request object. Why pass a Request object?

The main function is to get the token in the header, and then check, through the Exception thrown to get the specific error message back to the front end.

Public TokenService {/** * createToken * @return */ public String createToken(); /** * checkToken * @param request * @return */ public Boolean checkToken(HttpServletRequest request) throws Exception; }Copy the code

2. Token service implementation class

Token refers to the Redis service. The random algorithm tool class is used to generate a random UUID string to create the token, and then the token is put into redis (to prevent redundant data retention, the expiration time is set to 10000 seconds, depending on the service). If the token is successfully put into REDis, the token value is returned.

The checkToken method simply fetches the token from the header to the value (if not from the header, then from the paramter) and throws an exception if it does not exist. This exception message can be caught by the interceptor and returned to the front end.

@Servicepublic class TokenServiceImpl implements TokenService { @Autowired private RedisService redisService; @override public String createToken() {String STR = randomUtil.randomuuid (); StrBuilder token = new StrBuilder(); try { token.append(Constant.Redis.TOKEN_PREFIX).append(str); redisService.setEx(token.toString(), token.toString(), 10000L); boolean notEmpty = StrUtil.isNotEmpty(token.toString()); if (notEmpty) { return token.toString(); } } catch (Exception ex) { ex.printStackTrace(); } return null; } /** * checkToken ** @param request * @return */ @override public Boolean checkToken(HttpServletRequest request) throws Exception { String token = request.getHeader(Constant.TOKEN_NAME); If (strutil. isBlank(token)) {// If (strutil. isBlank(token)) {// Token = request. If (StrUtil isBlank (token)) {/ / parameter of the token does not exist in the throw new ServiceException (Constant) ResponseCode) ILLEGAL_ARGUMENT, 100); } } if (! redisService.exists(token)) { throw new ServiceException(Constant.ResponseCode.REPETITIVE_OPERATION, 200); } boolean remove = redisService.remove(token); if (! remove) { throw new ServiceException(Constant.ResponseCode.REPETITIVE_OPERATION, 200); } return true; }}Copy the code

Configuration of interceptors

1. Web configuration classes

Realize WebMvcConfigurerAdapter, main effect is to add autoIdempotentInterceptor to the configuration class, so we went to the interceptor.

Note the @Configuration annotation so that it can be added to the context when the container is started

@Configurationpublic class WebConfiguration extends WebMvcConfigurerAdapter { @Resource private AutoIdempotentInterceptor autoIdempotentInterceptor; Public void addInterceptors(InterceptorRegistry) { registry.addInterceptor(autoIdempotentInterceptor); super.addInterceptors(registry); }}Copy the code

2. Intercepting the processor

The main function is to intercept the scanned to AutoIdempotent annotations to the method, then call the checkToken() method of tokenService to verify whether the token is correct, if the exception is caught, render the exception information as JSON and return to the front end

/ * * * * / @ interceptor Componentpublic class AutoIdempotentInterceptor implements HandlerInterceptor {@autowired private TokenService tokenService; /** * Preprocessing ** @param request * @param Response * @param handler * @return * @throws Exception */ @override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (! (handler instanceof HandlerMethod)) { return true; } HandlerMethod handlerMethod = (HandlerMethod) handler; Method method = handlerMethod.getMethod(); AutoIdempotent Method.getannotation (AutoIdempotent. Class); // Scan AutoIdempotent method.getannotation (AutoIdempotent. if (methodAnnotation ! = null) { try { return tokenService.checkToken(request); // Check idempotent, pass the check, fail to throw an exception, } Catch (Exception ex) {ResultVo failedResult = resultVo.getFailedResult (101, ex. GetMessage ());} Catch (Exception ex) {ResultVo failedResult = ResultVo. writeReturnJson(response, JSONUtil.toJsonStr(failedResult)); throw ex; } // Return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {} /** * The returned JSON value * @param Response * @param json * @throws Exception */ private void writeReturnJson(HttpServletResponse response, String json) throws Exception { PrintWriter writer = null; response.setCharacterEncoding("UTF-8"); response.setContentType("text/html; charset=utf-8"); try { writer = response.getWriter(); writer.print(json); } catch (IOException e) { } finally { if (writer ! = null) writer.close(); }}}Copy the code

The test case

1. Simulate the business request class

First we need to retrieve the token via the /get/ Token path via the getToken() method, then we call the testIdempotence method annotated @Autoidempotent, the interceptor will intercept all the requests. The checkToken() method in TokenService is called when it is determined that the annotation is present on the method being processed. If an exception is caught, it is thrown to the caller.

Let’s simulate a request:

@RestControllerpublic class BusinessController { @Resource private TokenService tokenService; @Resource private TestService testService; @PostMapping("/get/token") public String getToken() { String token = tokenService.createToken(); if (StrUtil.isNotEmpty(token)) { ResultVo resultVo = new ResultVo(); resultVo.setCode(Constant.code_success); resultVo.setMessage(Constant.SUCCESS); resultVo.setData(token); return JSONUtil.toJsonStr(resultVo); } return StrUtil.EMPTY; } @AutoIdempotent @PostMapping("/test/Idempotence") public String testIdempotence() { String businessResult = testService.testIdempotence(); if (StrUtil.isNotEmpty(businessResult)) { ResultVo successResult = ResultVo.getSuccessResult(businessResult); return JSONUtil.toJsonStr(successResult); } return StrUtil.EMPTY; }}Copy the code

2, use postman request

First access the get/token path to obtain the specific token:

The first request is successful, and then the second request is made:

The second request is returned as a repeat operation, so we can make the first request succeed. The second request will fail:

conclusion

This article introduces the use of SpringBoot and interceptor redis to elegantly implement interface idempotent.

For idempotent in actual development process is very important, because an interface may be countless client calls, how to ensure that it does not affect the background of the business process, how to guarantee its impact data only once is very important, it can prevent the dirty data or data, also can reduce the concurrency value, are very good thing.

While the traditional approach is to judge the data every time, this approach is not intelligent and automation, more trouble. Today’s automation can also improve application scalability.

Wenyuan network, only for the use of learning, such as infringement, contact deletion.

For your convenience, I have also compiled a set of learning materials, covering Java virtual machine, Spring framework, Java threads, data structures, design patterns and so on, free for students who love Java ~

Focus on the public number [Java circle], quality articles delivered daily.