Springboot реализует проверку идемпотентности интерфейса

Java
Springboot реализует проверку идемпотентности интерфейса

Введение

Содержание этой статьи состоит в том, чтобы реализовать идемпотентную проверку интерфейса.Узнайте больше руководств по серии springboot для тех, кто ищет знания, и посмотрите официальный альбом учетной записи;Идемпотентность интерфейса, вообще говоря, и только один запрос завершается успешно, когда несколько запросов инициируются в в то же время., Его цель состоит в том, чтобы предотвратить множественные представления, повторное хранение данных, а также формировать задержки проверки сети и повторные представления; публика:искатель знаний

Искатель знаний (Наследуя дух открытого исходного кода, Распространение знаний о технологиях;)

Два плана реализации

Основные реализации следующие

2.1 Уникальный индекс

Добавление уникального индекса в таблицу является самым простым методом.При повторной вставке данных об исключении SQL будет сообщено напрямую, что мало повлияет на приложение;

alter table 表名 add unique(字段)

Например, два поля являются уникальными индексами.Если встречается точно такое же имя_заказа, create_time будет сообщать об исключении напрямую и повторно;

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

2.2 Блокировка

Распределенные блокировки также могут реализовывать идемпотентную проверку интерфейсов.Искатели знаний написали документ об идее использования Redis для реализации распределенных блокировок.Друзья могут обратиться к следующему«Почему бы вам не перераспределить распределенную блокировку? потому что вы не видели эту статью"

Используйте оптимистическую блокировку (реализованную на основе номера версии) или пессимистическую блокировку (блокировку таблицы или строки);

2.3 Сначала спроси, а потом суди

При входе на склад сначала запросите есть ли данные, нет вставки, иначе не вставится;

2.4 жетонный механизм

Механизм токена также находится в центре внимания этой статьи; общая идея состоит в том, чтобы сначала перейти к Redis, чтобы получить токен при инициировании запроса, поместить полученный токен в прослушиватель запроса, перехватить запрос, когда запрос достигает сервера, и проверить токен в прослушивателе запроса. , чтобы проверить, если проверка прошла, отменить перехват и удалить токен, в противном случае используйте пользовательское исключение для возврата сообщения об ошибке;

Три использования Redis для проверки идемпотентности интерфейса

3.1 класс инструментов Redis

Чтобы настроить RedisTemplate, обратитесь к статье, опубликованной Knowledge Seeker.«интегрированный redis Springboot (базовый)»

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

@Component
public class RedisUtils {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 判断key是否存在
     * @param key 键
     * @return boolean
     */
    public boolean hasKey(String key) {
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


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


    /**
     * 普通缓存获取
     * @param key 键
     * @return 值
     */

    public Object get(String key) {

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


    /**
     * 普通缓存放入并设置时间
     * @param key 键
     * @param value 值
     * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */

    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;
        }
    }

}

3.2 Класс инструмента токена

Используйте uuid для генерации случайной строки, предотвратите расшифровку токена через шифрование md5 и обеспечьте уникальность и безопасность токена; установите время истечения 30 секунд, то есть только один успешный запрос может быть отправлен в течение 30 секунд. В соответствии с различными потребностями бизнеса читатели справляются с этим сами;

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

    @Autowired
    RedisUtils redisUtils;

    // token 过期时间为30秒
    private final static Long TOKEN_EXPIRE = 30L;

    private final static String TOKEN_NAME = "token";
    /* *
     * @Author lsc
     * <p> 生成token 放入缓存</p>
     * @Param []
     */
    public String generateToken() {
        String uuid = UUID.randomUUID().toString();
        String token = DigestUtils.md5DigestAsHex(uuid.getBytes());
        redisUtils.set(TOKEN_NAME,token,TOKEN_EXPIRE);
        return token;
    }
    /* *
     * @Author lsc
     * <p> token 校验 </p>
     * @Param [request]
     */
    public boolean verifyToken(HttpServletRequest request) {
        String token = request.getHeader(TOKEN_NAME);
        // header中不存在token
        if(StringUtils.isEmpty(token)) {
           // 抛出自定义异常
            System.out.println("token不存在");
            throw new GlobleException(CodeMsg.BAD_REQUEST);
        }
        // 缓存中不出在 
        if(!redisUtils.hasKey(TOKEN_NAME)) {
            // 抛出自定义异常
            System.out.println("token已经过期");
            throw new GlobleException(CodeMsg.BAD_REQUEST);
        }
        String cachToekn = (String)redisUtils.get(TOKEN_NAME);
        if (!token.equals(cachToekn)){
            // 抛出自定义异常
            System.out.println("token校验失败");
            throw new GlobleException(CodeMsg.BAD_REQUEST);
        }
        // 移除token
        Boolean del = redisUtils.del(TOKEN_NAME);
        if (!del){
            // 抛出自定义异常
            System.out.println("token删除失败");
            throw new GlobleException(CodeMsg.BAD_REQUEST);
        }
        return true;
    }
}

3.3 Аннотации

Определите аннотации, которые используются в методах.Когда метод уровня управления аннотирован, это указывает, что запрос является идемпотентным запросом;

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

}

3.4 Конфигурация перехватчика

Выберите пре-перехватчик, и каждый запрос будет проверять, есть ли идемпотентная аннотация в прибывающем методе, и если да, то выполнять проверку токена;

/**
 * @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 (!(handler instanceof HandlerMethod)) {
            return true;
        }
        // 对有Idempotent注解的方法进行拦截校验
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        Idempotent methodAnnotation = method.getAnnotation(Idempotent.class);
        if (methodAnnotation != null) {
            // token 校验
            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 {

    }
}

Сопоставьте шаблон URL-адреса с перехватчиком и введите его в контейнер Spring.

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

    @Autowired
    IdempotentInterceptor idempotentInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 拦截所有请求
        registry.addInterceptor(idempotentInterceptor).addPathPatterns("/**");
    }

}

3.5 Уровень управления

Уровень управления написан следующим образом: при инициации запроса токен получается через метод getToken, полученный токен помещается в прослушиватель, а затем запрашивается метод testIdempotent.

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

    @Autowired
    TokenUtis tokenUtis;

    @GetMapping("zszxz/token")
    public ResultPage getToken(){
        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(){
        return ResultPage.sucess(CodeMsg.SUCESS,"校验成功");
    }
}

3.6 Тестирование

Запрос на получение токена

NHm27D.png

Токен, который был использован, запрос сообщает об ошибке

NHnpj0.md.png

Запрос на получение токена выполнен успешно

NHnKu6.md.png

Для высококонкурентных запросов можно использовать jmeter для тестирования, и эта статья также может быть реализована с использованием перехвата aop;

Этот набор руководств