In a real development project, where an exposed interface often faces many requests, let’s explain the concept of idempotence: Any number of executions has 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: The database establishes a unique index to ensure that only one data token can be inserted into the database. Each interface obtains a token before making a 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 can be deleted. Mysql > select * from innoDB; mysql > select * from innoDB; mysql > select * from innoDB First of all, the database is queried to see if there is any data. If there is any data, the request is rejected directly. If there is no data, it is proved that it is the first time to enter the database. Redis implements automatic idempotent:
Public number Java source code
Build redis service Api
The first is to set up the Redis server.
Redis stater from Springboot or Jedis from Spring can also be introduced. The main API used later is its set method and exists method. Here we use the redisTemplate encapsulated by SpringBoot
/** * Component public 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
Token service interface: We create a new interface to create the token service. There are mainly two methods, one for creating the token and the other for verifying the token. Creating a token is a string. Checking a token is a request object. Why pass a Request object? Public interface TokenService {TokenService: TokenService: TokenService: TokenService: TokenService: TokenService: TokenService: TokenService: TokenService: TokenService: TokenService: TokenService: TokenService: TokenService: TokenService: TokenService
/** * createToken * @return */ public String createToken(); /** * checkToken * @param request * @return */ public Boolean checkToken(HttpServletRequest request) throws Exception;Copy the code
} 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.
@Service public 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
Web Configuration class, realize WebMvcConfigurerAdapter, main effect is to add autoIdempotentInterceptor to the Configuration class, so the interceptor can we effect, pay attention to using the @ Configuration annotations, So you can add it to the context when the container starts
@Configuration public class WebConfiguration extends WebMvcConfigurerAdapter { @Resource private AutoIdempotentInterceptor autoIdempotentInterceptor; Public void addInterceptors(InterceptorRegistry) { registry.addInterceptor(autoIdempotentInterceptor); super.addInterceptors(registry); }} Intercepting handler: The tokenService checkToken() method is called to check whether the token is correct. If capture the anomaly is to paint exception information in json returned to the front / * * * * / @ interceptor Component public 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
To simulate the business request class, first we need to get the token through the /get/ Token path through the getToken() method to obtain the specific token, then we call the testIdempotence method annotated @Autoidempotent, the interceptor will intercept all the request. The checkToken() method in TokenService is called when the annotation is detected. If an exception is caught, it will throw the exception to the caller.
@RestController public 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
With postman request, first access the get/token path to obtain the token specific:
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 and the realization of the interceptor, redis to elegant springboot interface idempotent, for power and so on in the 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 business process, how to guarantee its impact data only once is very important, It prevents the generation of dirty or messy data, and also reduces concurrency, which is a very beneficial 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.
Source: https://zhuanlan.zhihu.com/p/356257577
Distributed theory: Simple Paxos algorithm