La anotación personalizada de Spring Boot implementa la exponenciación automática de la interfaz

En proyectos de desarrollo reales, una interfaz expuesta a menudo se enfrenta a muchas solicitudes. Expliquemos el concepto de idempotencia: el impacto de cualquier número de ejecuciones es el mismo que el impacto de una ejecución . Según este significado, el significado último es que el impacto en la base de datos solo puede ser una vez y no puede repetirse. La forma de asegurar su idempotencia suele tener los siguientes medios:

  1. La base de datos establece un índice único para garantizar que solo se inserte finalmente una pieza de datos en la base de datos

  2. Mecanismo de token, primero obtenga un token antes de cada solicitud de interfaz y luego agregue este token al cuerpo del encabezado de la solicitud en la siguiente solicitud y verifíquelo en segundo plano. Si la verificación pasa la eliminación del token, el token se juzgado de nuevo la próxima vez.

  3. Bloqueo pesimista o bloqueo optimista, el bloqueo pesimista puede garantizar que otros SQL no puedan actualizar los datos cada vez que se actualicen (cuando el motor de la base de datos es innodb, la condición de selección debe ser un índice único para evitar que se bloquee toda la tabla)

  4. Verifique primero y luego juzgue. Primero, verifique si hay datos en la base de datos. Si se ha solicitado el certificado de existencia, la solicitud se rechaza directamente. Si no existe, demuestra que es la primera vez que ingresa y deja va directamente.

Redis realiza el diagrama esquemático de la idempotencia automática:

 

Compilar la API del servicio redis

  • El primero es construir un servidor redis.

  • También es posible introducir el statuser de redis de springboot, o el jedis empaquetado por Spring. La API principal usada más adelante es su método set y existe método. Aquí usamos springboot empaquetado 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;

    }

}

AutoIdempotent de anotación personalizada

Personalizar una anotación. El propósito principal de definir esta anotación es agregarla a un método que necesita ser idempotente. Si un método es anotado, automáticamente será idempotente. Si esta anotación se escanea en segundo plano mediante la reflexión, procesará este método para lograr la idempotencia automática. Use la meta anotación ElementType.METHOD para indicar que solo se puede colocar en el método, y atenciónPolítica.RUNTIME indica que se está ejecutando

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

creación y verificación de tokens

  • Interfaz de servicio de token: Creamos una nueva interfaz para crear un servicio de token. Hay principalmente dos métodos, uno se usa para crear el token y el otro se usa para verificar el token. La creación de un token es principalmente una cadena, y la verificación del token es principalmente para transmitir el objeto de la solicitud. ¿Por qué desea pasar el objeto de la solicitud? La función principal es obtener el token en el encabezado y luego verificar, obtener el mensaje de error específico a través de la excepción lanzada y devolverlo al front-end

public interface TokenService {

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

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

}
  • Clase de implementación de servicio del token: el token se refiere al servicio redis, y la creación del token utiliza una clase de herramienta de algoritmo aleatorio para generar una cadena uuid aleatoria, y luego la coloca en redis (para evitar la retención redundante de datos, el vencimiento El tiempo se establece en 10000 segundos, que puede ser específico según la empresa), si la ubicación es exitosa, el valor del token se devolverá al final. El método checkToken es llevar el token al valor del encabezado (si no está disponible en el encabezado, obténgalo del parámetro), si no existe, lanza una excepción directamente. Esta información de excepción puede ser capturada por el interceptor y luego devuelta al front-end.

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

Configuración del interceptor

  • La clase de configuración web, que implementa WebMvcConfigurerAdapter, se usa principalmente para agregar autoIdempotentInterceptor a la clase de configuración, para que el interceptor pueda tener efecto. Preste atención a la anotación @Configuration, para que se pueda agregar al contexto cuando se inicia el contenedor.

@Configuration
public class WebConfiguration extends WebMvcConfigurerAdapter {

    @Resource
   private AutoIdempotentInterceptor autoIdempotentInterceptor;

    /**
     * 添加拦截器
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(autoIdempotentInterceptor);
        super.addInterceptors(registry);
    }
}
  • Procesador de interceptación: la función principal es interceptar y escanear AutoIdempotent al método de anotación y luego llamar al método checkToken () de tokenService para verificar si el token es correcto. Si se detecta una excepción, la información de la excepción se procesará en json y regresó al frente

/**
 * 拦截器
 */
@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();
        }
    }

}

Caso de prueba

  • Para simular la clase de solicitud de negocio, primero necesitamos obtener el token específico a través del método getToken () a través de la ruta / get / token, y luego llamamos al método testIdempotence. Este método está anotado con @AutoIdempotent, y el interceptor interceptará todas las solicitudes. Cuando haya una anotación en el método de procesamiento, se llamará al método checkToken () en TokenService. Si se detecta una excepción, la excepción se enviará a la persona que llama. Simulemos la solicitud a continuación:

@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;
    }
}
  • Para usar la solicitud del cartero, primero visite la ruta get / token para obtener un token específico:

  • Úselo para obtener el token y luego colóquelo en el encabezado de solicitud específico, puede ver que la primera solicitud es exitosa y luego solicitamos la segunda vez:

  • La segunda solicitud, la devolución es una operación repetitiva, se puede ver que se pasa la verificación repetitiva, y solo dejaremos que tenga éxito en la primera vez y fallar en la segunda vez:

para resumir

Este blog presenta el uso de springboot, interceptores y redis para implementar con elegancia la idempotencia de la interfaz. Es muy importante para la idempotencia en el proceso de desarrollo real, porque innumerables clientes pueden llamar a una interfaz. Cómo asegurarse de que no influya en el fondo En el procesamiento empresarial, es muy importante asegurarse de que solo afecte a los datos una vez. Puede evitar la generación de datos sucios o desordenados, y también puede reducir la cantidad de simultaneidad, lo que en realidad es muy beneficioso. El método tradicional consiste en juzgar los datos cada vez, pero este método no es lo suficientemente inteligente y automático, lo que es más problemático. Y el procesamiento automatizado actual también puede mejorar la escalabilidad del programa.

Supongo que te gusta

Origin blog.csdn.net/baidu_39322753/article/details/107615157
Recomendado
Clasificación