L'annotation personnalisée Spring Boot implémente l'exponentiation automatique de l'interface

Dans les projets de développement réels, une interface exposée fait souvent face à de nombreuses demandes.Expliquons le concept d'idempotence: l'impact d'un nombre quelconque d'exécutions est le même que celui d'une exécution . Selon ce sens, le sens ultime est que l'impact sur la base de données ne peut être qu'une seule fois et ne peut être répété. Comment assurer son idempotence a généralement les moyens suivants:

  1. La base de données établit un index unique pour garantir qu'une seule donnée est finalement insérée dans la base de données

  2. Mécanisme de jeton, obtenez d'abord un jeton avant chaque demande d'interface, puis ajoutez ce jeton au corps de l'en-tête de la demande dans la requête suivante et vérifiez-le en arrière-plan. Si la vérification réussit la suppression du jeton, le jeton sera jugé à nouveau la prochaine fois.

  3. Verrou pessimiste ou verrou optimiste, le verrou pessimiste peut garantir qu'un autre SQL ne peut pas mettre à jour les données à chaque fois pour la mise à jour (lorsque le moteur de base de données est innodb, la condition de sélection doit être un index unique pour empêcher toute la table d'être verrouillée)

  4. Vérifiez d'abord, puis jugez. Tout d'abord, vérifiez s'il y a des données dans la base de données. Si le certificat d'existence a été demandé, la demande est directement rejetée. S'il n'existe pas, cela prouve que c'est la première fois d'entrer et de laisser ça va directement.

Redis réalise le schéma de principe de l'idempotence automatique:

 

Construire l'API du service Redis

  • La première consiste à créer un serveur Redis.

  • Il est également possible d'introduire le statuser redis à partir de springboot, ou les jedis packagés par Spring. L'API principale utilisée plus tard est sa méthode set et la méthode existe. Ici, nous utilisons springboot packaged redisTemplate

/**
 * redis工具类
 */
@Component
public class RedisService {

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 写入缓存
     * @param key
     * @param value
     * @return
     */
    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;
    }


    /**
     * 写入缓存设置时效时间
     * @param key
     * @param value
     * @return
     */
    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;
    }


    /**
     * 判断缓存中是否有对应的value
     * @param key
     * @return
     */
    public boolean exists(final String key) {
        return redisTemplate.hasKey(key);
    }

    /**
     * 读取缓存
     * @param key
     * @return
     */
    public Object get(final String key) {
        Object result = null;
        ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
        result = operations.get(key);
        return result;
    }

    /**
     * 删除对应的value
     * @param key
     */
    public boolean remove(final String key) {
        if (exists(key)) {
            Boolean delete = redisTemplate.delete(key);
            return delete;
        }
        return false;

    }

}

Annotation personnalisée AutoIdempotent

Personnalisez une annotation. L'objectif principal de la définition de cette annotation est de l'ajouter à une méthode qui doit être idempotente. Si une méthode est annotée, elle sera automatiquement idempotente. Si cette annotation est analysée en arrière-plan à l'aide de la réflexion, elle traitera cette méthode pour obtenir une idempotence automatique. Utilisez la méta-annotation ElementType.METHOD pour indiquer qu'elle ne peut être placée que sur la méthode et attentionPolicy.RUNTIME indique qu'elle est en cours d'exécution

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

création et vérification de jetons

  • Interface de service de jeton: nous créons une nouvelle interface pour créer un service de jeton. Il existe principalement deux méthodes, l'une est utilisée pour créer le jeton et l'autre est utilisée pour vérifier le jeton. La création d'un jeton est principalement une chaîne, et la vérification du jeton consiste principalement à transmettre l'objet de requête. Pourquoi voulez-vous transmettre l'objet de requête? La fonction principale est d'obtenir le jeton dans l'en-tête, puis de vérifier, d'obtenir le message d'erreur spécifique via l'exception levée et de le renvoyer au frontal

public interface TokenService {

    /**
     * 创建token
     * @return
     */
    public  String createToken();

    /**
     * 检验token
     * @param request
     * @return
     */
    public boolean checkToken(HttpServletRequest request) throws Exception;

}
  • Classe d'implémentation du service du jeton: le jeton fait référence au service redis, et la création du jeton utilise une classe d'outils d'algorithme aléatoire pour générer une chaîne uuid aléatoire, puis la met dans redis (afin d'éviter la rétention redondante des données, l'expiration time est fixé à 10000 secondes, ce qui peut être spécifique selon l'entreprise), si le placement est réussi, la valeur du jeton sera renvoyée à la fin. La méthode checkToken consiste à obtenir le jeton à la valeur de l'en-tête (s'il n'est pas disponible dans l'en-tête, récupérez-le à partir du paramètre), s'il n'existe pas, lancez une exception directement. Ces informations d'exception peuvent être capturées par l'intercepteur, puis renvoyées au frontal.

@Service
public class TokenServiceImpl implements TokenService {

    @Autowired
    private RedisService redisService;


    /**
     * 创建token
     *
     * @return
     */
    @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;
    }


    /**
     * 检验token
     *
     * @param request
     * @return
     */
    @Override
    public boolean checkToken(HttpServletRequest request) throws Exception {

        String token = request.getHeader(Constant.TOKEN_NAME);
        if (StrUtil.isBlank(token)) {// header中不存在token
            token = request.getParameter(Constant.TOKEN_NAME);
            if (StrUtil.isBlank(token)) {// parameter中也不存在token
                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;
    }
}

Configuration de l'intercepteur

  • La classe de configuration Web, qui implémente WebMvcConfigurerAdapter, est principalement utilisée pour ajouter autoIdempotentInterceptor à la classe de configuration, afin que l'intercepteur puisse prendre effet. Faites attention à l'annotation @Configuration, afin qu'elle puisse être ajoutée au contexte lorsque le conteneur est démarré

@Configuration
public class WebConfiguration extends WebMvcConfigurerAdapter {

    @Resource
   private AutoIdempotentInterceptor autoIdempotentInterceptor;

    /**
     * 添加拦截器
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(autoIdempotentInterceptor);
        super.addInterceptors(registry);
    }
}
  • Processeur d'interception: la fonction principale est d'intercepter et d'analyser AutoIdempotent vers la méthode d'annotation, puis d'appeler la méthode checkToken () de tokenService pour vérifier si le jeton est correct. Si une exception est interceptée, les informations d'exception seront rendues dans json et retourné à l'avant

/**
 * 拦截器
 */
@Component
public class AutoIdempotentInterceptor implements HandlerInterceptor {

    @Autowired
    private TokenService tokenService;

    /**
     * 预处理
     *
     * @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();
        //被ApiIdempotment标记的扫描
        AutoIdempotent methodAnnotation = method.getAnnotation(AutoIdempotent.class);
        if (methodAnnotation != null) {
            try {
                return tokenService.checkToken(request);// 幂等性校验, 校验通过则放行, 校验失败则抛出异常, 并通过统一异常处理返回友好提示
            }catch (Exception ex){
                ResultVo failedResult = ResultVo.getFailedResult(101, ex.getMessage());
                writeReturnJson(response, JSONUtil.toJsonStr(failedResult));
                throw ex;
            }
        }
        //必须返回true,否则会被拦截一切请求
        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 {

    }

    /**
     * 返回的json值
     * @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();
        }
    }

}

Cas de test

  • Pour simuler la classe de demande métier, nous devons d'abord obtenir le jeton spécifique via la méthode getToken () via le chemin / get / token, puis nous appelons la méthode testIdempotence. Cette méthode est annotée avec @AutoIdempotent, et l'intercepteur interceptera toutes les demandes. Lorsqu'il y a une annotation sur la méthode de traitement, la méthode checkToken () de TokenService sera appelée. Si une exception est interceptée, l'exception sera renvoyée à l'appelant. Simulons la demande ci-dessous:

@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;
    }
}
  • Pour utiliser la demande du facteur, visitez d'abord le chemin get / token pour obtenir un jeton spécifique:

  • Utilisez pour obtenir le jeton, puis placez-le dans l'en-tête de demande spécifique, vous pouvez voir que la première demande est réussie, puis nous demandons la deuxième fois:

  • La deuxième requête, le retour est une opération répétitive, on voit que la vérification répétitive est réussie, et nous ne la laisserons réussir que la première fois et échouer la deuxième fois:

Pour résumer

Ce blog présente l'utilisation de springboot, d'intercepteurs et de redis pour implémenter avec élégance l'idempotence d'interface. Il est très important pour l'idempotence dans le processus de développement réel, car une interface peut être appelée par d'innombrables clients. Comment s'assurer qu'elle n'influence pas l'arrière-plan traitement commercial, comment s'assurer qu'il n'affecte qu'une seule fois les données est très important. Cela peut empêcher la génération de données sales ou désordonnées, et cela peut également réduire la quantité de concurrence, ce qui est en fait une chose très bénéfique. La méthode traditionnelle est de juger les données à chaque fois. Cette méthode n'est pas assez intelligente et automatique, ce qui est plus gênant. Et le traitement automatisé d'aujourd'hui peut également améliorer l'évolutivité du programme.

Je suppose que tu aimes

Origine blog.csdn.net/baidu_39322753/article/details/107615157
conseillé
Classement