A preface

The content of this content is to achieve interface idempotent check, learning knowledge seekers more springboot system tutorial see the public number album; Interface idempotence is commonly referred to as multiple requests initiated at the same time and only one successful request; Its purpose is to prevent multiple submission, repeated data entry, form verification network delay repeated submission and other problems; Public account: Knowledge seeker

Inheriting the spirit of open Source, Spreading technology knowledge;

Ii. Implementation Scheme

The mainstream implementation scheme is as follows

2.1 Unique Index

Adding a unique index to a table is the simplest sub-method. When data is repeatedly inserted, SQL exceptions are reported directly, which has little impact on applications.

Alter table alter table add unique

For example, if the two fields are unique indexes, create_time will repeat the exception if the same order_NAME is present.

alter table `order`  add unique(order_name,create_time)
Copy the code

2.2 the lock

Distributed lock can also implement interface idempotent check, knowledge finder has written a document using Redis to implement distributed lock ideas, friends can refer to “why you don’t know redis distributed lock? Because you didn’t see this article.”

Use optimistic locking (based on version number implementation), or pessimistic locking (table or row locking) implementation;

2.3 Search before judging

Check whether the data exists in the database first. If no data is inserted, the data will not be inserted.

2.4 token mechanism

The token mechanism is the focus of this article; The general implementation idea is to first obtain the token from Redis when initiating a request, put the obtained token into the hearder of the request, intercept the request when the request reaches the server, verify the token in the hearder of the request, and release the interception if the verification passes. Delete the token. Otherwise, custom exceptions are used to return error messages.

3. Use Redis to realize interface idempotency check

3.1 Redis utility class

For the configuration of RedisTemplate, please refer to the article “Springboot Integration redis(Basics)” published by Knowledge Seeker.

/ * * *@Author lsc
 * <p> </p>
 */

@Component
public class RedisUtils {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /** * Check whether the key exists *@paramThe key key *@return boolean
     */
    public boolean hasKey(String key) {
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
            return false; }}/** * delete key *@paramThe key key * /
    public Boolean del(String key) {
        if(key ! =null && key.length() > 0) {
            return redisTemplate.delete(key);
        }else  {
            return false; }}/** * Common cache fetch *@paramThe key key *@returnValue * /

    public Object get(String key) {

        return key == null ? null : redisTemplate.opsForValue().get(key);
    }


    /** * Normal cache is placed and set time *@paramThe key key *@paramThe value value *@paramTime Time (s) Time must be greater than 0. If time is less than or equal to 0, the value is set indefinitely *@returnTrue Successful false failed */

    public boolean set(String key, Object value, long time) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false; }}}Copy the code

3.2 Token Utility class

Uuid is used to generate random strings and MD5 encryption is used to prevent token decryption to ensure uniqueness and security of token. , set the expiration time to 30 seconds, that is, only one successful request can be submitted within 30 seconds. Readers can handle the request according to different business requirements.

/ * * *@Author lsc
 * <p> </p>
 */
@Component
public class TokenUtis {

    @Autowired
    RedisUtils redisUtils;

    // The token expires within 30 seconds
    private final static Long TOKEN_EXPIRE = 30L;

    private final static String TOKEN_NAME = "token";
    / * * * @ Author LSC * < p > to generate the token into the cache < / p > * @ Param [] * /
    public String generateToken(a) {
        String uuid = UUID.randomUUID().toString();
        String token = DigestUtils.md5DigestAsHex(uuid.getBytes());
        redisUtils.set(TOKEN_NAME,token,TOKEN_EXPIRE);
        return token;
    }
    /* * * @author LSC * 

Token verification

* @param [request] */
public boolean verifyToken(HttpServletRequest request) { String token = request.getHeader(TOKEN_NAME); // No token exists in header if(StringUtils.isEmpty(token)) { // Throw a custom exception System.out.println("Token does not exist"); throw new GlobleException(CodeMsg.BAD_REQUEST); } // The cache does not exist if(! redisUtils.hasKey(TOKEN_NAME)) {// Throw a custom exception System.out.println("Token has expired"); throw new GlobleException(CodeMsg.BAD_REQUEST); } String cachToekn = (String)redisUtils.get(TOKEN_NAME); if(! token.equals(cachToekn)){// Throw a custom exception System.out.println("Token verification failed"); throw new GlobleException(CodeMsg.BAD_REQUEST); } / / remove the token Boolean del = redisUtils.del(TOKEN_NAME); if(! del){// Throw a custom exception System.out.println("Token deletion failed"); throw new GlobleException(CodeMsg.BAD_REQUEST); } return true; }}Copy the code

3.3 annotations

Define annotations, used on methods, to indicate that the request is idempotent when annotated on methods of the control layer.

/ * * *@Author lsc
 * <p> </p>
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {

}
Copy the code

3.4 Interceptor configuration

The pre-interceptor is selected to verify whether there are idempotent annotations on the method arrived in each request, and token verification is performed if there are.

/ * * *@Author lsc
 * <p> </p>
 */
@Component
public class IdempotentInterceptor implements HandlerInterceptor {

    @Autowired
    private TokenUtis tokenUtis;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        if(! (handlerinstanceof HandlerMethod)) {
            return true;
        }
        // Intercept validation of methods with Idempotent annotations
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        Idempotent methodAnnotation = method.getAnnotation(Idempotent.class);
        if(methodAnnotation ! =null) {
            / / token validation
            tokenUtis.verifyToken(request);
        }
        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 {}}Copy the code

Url pattern matching is performed on the interceptor and injected into the Spring container

/ * * *@Author lsc
 * <p> </p>
 */
@Configuration
public class WebConfiguration implements WebMvcConfigurer {

    @Autowired
    IdempotentInterceptor idempotentInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // Intercept all requests
        registry.addInterceptor(idempotentInterceptor).addPathPatterns("/ * *"); }}Copy the code

3.5 control layer

The control layer is programmed as follows: When initiating a request, obtain the token through the getToken method, put the obtained token into the Hearder, and then request the testIdempotent method

/ * * *@Author lsc
 * <p> </p>
 */
@RestController
public class ZszxzController {

    @Autowired
    TokenUtis tokenUtis;

    @GetMapping("zszxz/token")
    public ResultPage getToken(a){
        String token = tokenUtis.generateToken();
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("token",token);
        return ResultPage.sucess(CodeMsg.SUCESS,jsonObject);
    }

    @Idempotent
    @GetMapping("zszxz/test")
    public ResultPage testIdempotent(a){
        return ResultPage.sucess(CodeMsg.SUCESS,"Verification successful"); }}Copy the code

3.6 test

Request to Obtain token

An error message was reported when the token was consumed

Succeeded in obtaining the token request again

High concurrency requests can be tested using JMeter, and this article can also be implemented using AOP interception;

This set of tutorials

  • Springboot introduction (1)
  • Springboot Custom banner(2)
  • Springboot configuration file parsing (3)
  • Springboot integration mybatis (4)
  • JdbcTemplate springboot integration (5)
  • Spingboot Unit Test (6)
  • Springboot integration thymeleaf (7)
  • Springboot Multi-file upload (8)
  • Springboot file download (9)
  • Springboot Custom exception Class (10)
  • Springboot Multi-environment Configuration (11)
  • Springboot Automatic configuration principle (12)
  • Springboot integrated with restTemplate interface call (13)
  • Springboot Integrated Task Scheduling (14)
  • Springboot Cross-domain CORS Processing (15)
  • Springboot enables GIZP compression (16)
  • Springboot integration logback (17)
  • Springboot integration Swagger (18)
  • Springboot Integrated Actuator Monitoring (19)
  • Springboot integration mybatis + oracle + druid (20)
  • Springboot integration springsession (21)
  • JWT springboot integration (22)
  • Springboot integration with admin background Monitoring (23)
  • Springboot Integration redis Basics (24)
  • Redis Cache with SpringBoot
  • Springboot uses AOP log interception (26)
  • Springboot integration Validation (27)
  • Springboot integration mybatisPlus (28)
  • Springboot integration shiro (29)
  • Springboot implementation of interface parity check (30)
  • Springboot – integrated web sockets (31)
  • RestTemplate (32)
  • SpringBoot uses @async asynchronous calls with thread pools (33)
  • To be continued