Spring boot implements interface equal power verification

Posted by rpieszak on Mon, 10 Jan 2022 06:23:10 +0100

Generally speaking, interface equipower means that only one request is successful when multiple requests are initiated at the same time; Its purpose is to prevent multiple submission, repeated data warehousing, form verification, network delay, repeated submission and other problems.

The mainstream implementation scheme is as follows:

1. Unique index: add a unique index to the table. This method is the simplest. When data is repeatedly inserted, sql exceptions will be reported directly, which has little impact on the application;

alter table table name add unique (field)

For example, two fields are unique indexes. If exactly the same order appears_ name,create_ Time will directly and repeatedly report exceptions;

alter table 'order' add unique(order_name,create_time)

2. Query before judgment: query whether there is this data before receipt. If not, insert it. Otherwise, do not insert;

3. Token mechanism: when initiating a request, first go to redis to obtain the token and put the obtained token into the requested hearder. When the request reaches the server, intercept the request and verify the token in the requested hearder. If the verification passes, release the interception and delete the token. Otherwise, use a user-defined exception to return error information.

Step 1: write redis tool class

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;

@Component
public class RedisUtils {

    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

    /**
     * Determine whether the key exists
     * @param key key
     * @return
     */
    public boolean hasKey(String key){
        try {
            return redisTemplate.hasKey(key);
        }catch (Exception e){
            e.printStackTrace();
            return false;
        }
    }

    /**
     * Delete key
     * @param key key
     * @return
     */
    public Boolean del(String key){
        if (key != null && key.length() > 0){
            return redisTemplate.delete(key);
        }else {
            return false;
        }
    }

    /**
     * Normal cache fetch
     * @param key key
     * @return
     */
    public Object get(String key){
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    /**
     * Normal cache put and set time
     * @param key key
     * @param value value
     * @param time Time (seconds) time must be greater than 0. If time is less than or equal to 0, the infinite period will be set
     * @return
     */
    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;
        }
    }

}

Step 2: write token tool class

import com.smile.project.exception.utils.CodeMsg;
import com.smile.project.exception.utils.CommonException;
import io.netty.util.concurrent.GlobalEventExecutor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import java.util.UUID;

/**
 * Using uuid to generate random strings,
 * md5 encryption prevents the token from being decrypted and ensures the uniqueness and security of the token
 * Set the expiration time to 30 seconds, that is, you can submit a successful request within 30 seconds
 */
@Component
public class TokenUtils {

    @Autowired
    RedisUtils redisUtils;

    //The token expiration time is 30 seconds
    private final static Long TOKEN_EXPIRE = 30L;

    private final static String TOKEN_NAME = "token";

    /**
     * Generate a token and put it into the cache
     */
    public String generateToken(){
        String uuid = UUID.randomUUID().toString();
        String token = DigestUtils.md5DigestAsHex(uuid.getBytes());
        redisUtils.set(TOKEN_NAME,token,TOKEN_EXPIRE);
        return token;
    }

    /**
     * token check
     */
    public boolean verifyToken(HttpServletRequest request){
        String token = request.getHeader(TOKEN_NAME);
        //token does not exist in header
        if (StringUtils.isEmpty(token)){
            //Throw custom exception
            System.out.println("token non-existent");
            throw new CommonException(CodeMsg.NOT_TOKEN);
        }
        //Does not exist in cache
        if (!redisUtils.hasKey(TOKEN_NAME)){
            System.out.println("token Has expired");
            throw new CommonException(CodeMsg.TIME_OUT_TOKEN);
        }
        String cachToken = (String) redisUtils.get(TOKEN_NAME);
        if (!token.equals(cachToken)){
            System.out.println("token Inspection failed");
            throw new CommonException(CodeMsg.VALIDA_ERROR_TOKEN);
        }
        //Remove token
        Boolean del = redisUtils.del(TOKEN_NAME);
        if (!del){
            System.out.println("token Deletion failed");
            throw new CommonException(CodeMsg.DEL_ERROR_TOKEN);
        }
        return true;
    }

}

Step 3: define the annotation and use it on the method. When it is annotated on the method of the control layer, it indicates that the request is an equal power request

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * When annotated on the method of the control layer, it indicates that the request is an equal power request
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
}

Step 4: interceptor configuration. Select the front interceptor, and check whether there is an equal power annotation on the arriving method every request. If so, perform token verification

import com.smile.project.redis.utils.Idempotent;
import com.smile.project.redis.utils.TokenUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;

@Component
public class IdempotentInterceptor implements HandlerInterceptor {

    @Autowired
    private TokenUtils tokenUtils;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (!(handler instanceof HandlerMethod)){
            return true;
        }
        //Intercept and verify the methods annotated with Idempotent
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        Idempotent methodAnnotation = method.getAnnotation(Idempotent.class);
        if (methodAnnotation != null){
            //token verification
            tokenUtils.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 {

    }
}

Step 5: match the url pattern of the interceptor and inject it into the spring container

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * Perform url pattern matching on the interceptor and inject it into the spring container
 */
@Configuration
public class WebConfiguration implements WebMvcConfigurer {

    @Autowired
    IdempotentInterceptor idempotentInterceptor;

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

Step 6: control layer

Write the control layer, obtain the token through the getToken method when initiating the request, put the obtained token into the hearder, and then request the specific method. When requesting a specific method normally, pay attention to adding a token in the hearder, otherwise it will fail

import com.alibaba.fastjson.JSONObject;
import com.smile.project.exception.utils.CodeMsg;
import com.smile.project.exception.utils.ResultPage;
import com.smile.project.redis.utils.Idempotent;
import com.smile.project.redis.utils.TokenUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class SmileController {

    @Autowired
    TokenUtils tokenUtils;

    @GetMapping("smile/token")
    public ResultPage getToken(){
        String token = tokenUtils.generateToken();
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("token",token);
        return ResultPage.success(CodeMsg.SUCCESS,jsonObject);
    }

    @Idempotent
    @GetMapping("smile/test")
    public ResultPage testIdempotent(){
        return ResultPage.success(CodeMsg.SUCCESS,"Verification successful");
    }

}

Topics: Java Spring Boot Back-end