Esta es la postura correcta de la autenticación de inicio de sesión unificada, el manejo de excepciones y el formato de datos de SpringBoot

Este artículo aprenderá el módulo de procesamiento de funciones unificadas de Spring Boot, que también es la parte real de AOP

  • Interfaz de implementación de verificación de permiso de inicio de sesión de usuario  HandlerInterceptor + WebMvcConfigurer

  • El manejo de excepciones usa anotaciones  @RestControllerAdvice + @ExceptionHandler

  • El formato de datos regresa usando anotaciones  @ControllerAdvice e implementa la interfaz @ResponseBodyAdvice

1. Verificación de autoridad de inicio de sesión de usuario unificado

El proceso de desarrollo y mejora de la autoridad de inicio de sesión del usuario

  • Verificación de inicio de sesión de usuario inicial: Obtenga la sesión y la información del usuario en la sesión en cada método. Si hay un usuario, entonces el inicio de sesión se considera exitoso; de lo contrario, el inicio de sesión falla

  • La segunda versión de verificación de inicio de sesión de usuario: proporcione un método unificado, llame al método de verificación de identidad de inicio de sesión de usuario unificado en cada método que deba verificarse para juzgar

  • La tercera versión de validación de inicio de sesión de usuario: use Spring AOP para realizar una validación de inicio de sesión de usuario unificada

  • La cuarta versión de verificación de inicio de sesión de usuario: uso de interceptores Spring para lograr una verificación de inicio de sesión unificada para los usuarios

1.1 La verificación inicial de la autoridad de inicio de sesión del usuario

 

@RestController
@RequestMapping("/user")
public class UserController {

    @RequestMapping("/a1")
    public Boolean login (HttpServletRequest request) {
        // 有 Session 就获取,没有就不创建
        HttpSession session = request.getSession(false);
        if (session != null && session.getAttribute("userinfo") != null) {
            // 说明已经登录,进行业务处理
            return true;
        } else {
            // 未登录
            return false;
        }
    }

    @RequestMapping("/a2")
    public Boolean login2 (HttpServletRequest request) {
        // 有 Session 就获取,没有就不创建
        HttpSession session = request.getSession(false);
        if (session != null && session.getAttribute("userinfo") != null) {
            // 说明已经登录,进行业务处理
            return true;
        } else {
            // 未登录
            return false;
        }
    }
}

El código escrito de esta manera tiene la misma autoridad de verificación de inicio de sesión de usuario en cada método. Las desventajas son:

  • Cada método debe escribir el método de verificación de inicio de sesión del usuario por separado, incluso si está encapsulado en un método público, todavía necesita pasar parámetros y llamar y juzgar en el método.

  • Cuantos más controladores se agreguen, más métodos se llamarán para la verificación de inicio de sesión del usuario, lo que aumenta el éxito de la modificación y el mantenimiento posteriores.

  • Estos métodos de verificación de inicio de sesión de usuario no tienen casi nada que ver con el negocio que se implementará ahora, pero cada método debe escribirse nuevamente, por lo que es una muy buena solución para proporcionar un método AOP público para el método de verificación de autoridad de inicio de sesión de usuario unificado.

1.2 Autenticación de inicio de sesión de usuario unificado de Spring AOP

Autenticación de inicio de sesión de usuario unificado, el primer pensamiento del método de implementación es usar la notificación previa de Spring AOP o la notificación envolvente para lograr

 

@Aspect // 当前类是一个切面
@Component
public class UserAspect {
    // 定义切点方法 Controller 包下、子孙包下所有类的所有方法
    @Pointcut("execution(* com.example.springaop.controller..*.*(..))")
    public void  pointcut(){}
    
    // 前置通知
    @Before("pointcut()")
    public void doBefore() {}
    
    // 环绕通知
    @Around("pointcut()")
    public Object doAround(ProceedingJoinPoint joinPoint) {
        Object obj = null;
        System.out.println("Around 方法开始执行");
        try {
            obj = joinPoint.proceed();
        } catch (Throwable e) {
            e.printStackTrace();
        }
        System.out.println("Around 方法结束执行");
        return obj;
    }
}

Sin embargo, si la función de verificación del permiso de inicio de sesión del usuario solo se implementa en el aspecto del código anterior Spring AOP, hay dos problemas:

  • No hay forma de obtener  HttpSession y Solicitar objeto

  • Necesitamos interceptar algunos métodos, pero no otros métodos. Por ejemplo, el método de registro y el método de inicio de sesión no se interceptan, es decir, las reglas de interceptación reales son muy complicadas y las expresiones simples de aspectJ no pueden cumplir con los requisitos de interceptación.

1.3 Interceptores de resorte

En respuesta al problema de Spring AOP en el código anterior, Spring proporciona un interceptor de implementación específico: HandlerInterceptorla implementación del interceptor tiene dos pasos:

1. Cree un interceptor personalizado e implemente  HandlerInterceptor el método preHandle en la interfaz en Spring

2. Agregue el interceptor personalizado a la configuración del marco y establezca las reglas de interceptación

  • @Configuration Anotar la clase actual 

  • implementar  WebMvcConfigurer la interfaz

  • addInterceptors método de anulación 

Nota: Se pueden configurar varios interceptores en un proyecto al mismo tiempo 

(1) Crear un interceptor personalizado

/**
 * @Description: 自定义用户登录的拦截器
 * @Date 2023/2/13 13:06
 */
@Component
public class LoginIntercept implements HandlerInterceptor {
    // 返回 true 表示拦截判断通过,可以访问后面的接口
    // 返回 false 表示拦截未通过,直接返回结果给前端
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                             Object handler) throws Exception {
        // 1.得到 HttpSession 对象
        HttpSession session = request.getSession(false);
        if (session != null && session.getAttribute("userinfo") != null) {
            // 表示已经登录
            return true;
        }
        // 执行到此代码表示未登录,未登录就跳转到登录页面
        response.sendRedirect("/login.html");
        return false;
    }
}

(2) Agregue el interceptor personalizado a la configuración del sistema y establezca las reglas de interceptación

  • addPathPatterns: indica la URL que se debe interceptar e **indica que se interceptan todos los métodos

  • excludePathPatterns: Indica la URL que debe ser excluida

Descripción: las reglas de interceptación pueden interceptar las URL utilizadas en este proyecto, incluidos los archivos estáticos (archivos de imagen, archivos JS y CSS, etc.).

/**
 * @Description: 将自定义拦截器添加到系统配置中,并设置拦截的规则
 * @Date 2023/2/13 13:13
 */
@Configuration
public class AppConfig implements WebMvcConfigurer {

    @Resource
    private LoginIntercept loginIntercept;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
//        registry.addInterceptor(new LoginIntercept());//可以直接new 也可以属性注入
        registry.addInterceptor(loginIntercept).
                addPathPatterns("/**").    // 拦截所有 url
                excludePathPatterns("/user/login"). //不拦截登录注册接口
                excludePathPatterns("/user/reg").
                excludePathPatterns("/login.html").
                excludePathPatterns("/reg.html").
                excludePathPatterns("/**/*.js").
                excludePathPatterns("/**/*.css").
                excludePathPatterns("/**/*.png").
                excludePathPatterns("/**/*.jpg");
    }
}

1.4 Ejercicio: Interceptor de inicio de sesión

Requerir

  • Las páginas de inicio de sesión y registro no están bloqueadas, otras páginas están bloqueadas

  • Después de que el inicio de sesión se escribe con éxito en la sesión, se puede acceder a la página interceptada normalmente

En 1.3, se creó y agregó un interceptor personalizado a la configuración del sistema, y ​​se establecieron las reglas para la interceptación.

(2) Cree  controller un paquete, créelo en el paquete  UserControllery escriba el código comercial de la página de inicio de sesión y la página de inicio 

 

@RestController
@RequestMapping("/user")
public class UserController {

    @RequestMapping("/login")
    public boolean login(HttpServletRequest request,String username, String password) {
        boolean result = false;
        if (StringUtils.hasLength(username) && StringUtils.hasLength(password)) {
            if(username.equals("admin") && password.equals("admin")) {
                HttpSession session = request.getSession();
                session.setAttribute("userinfo","userinfo");
                return true;
            }
        }
        return result;
    }

    @RequestMapping("/index")
    public String index() {
        return "Hello Index";
    }
}

(3) Ejecute el programa, visite la página y compare el efecto antes y después de iniciar sesión

 

1.5 Principio de implementación del interceptor

Con el interceptor se realizará el procesamiento comercial correspondiente antes de llamar al Controller, el proceso de ejecución se muestra en la siguiente figura

Análisis del código fuente del principio de implementación

Toda  la ejecución se  logrará Controller a través de un programador. DispatcherServlet

 

 Y todos los métodos se ejecutarán  DispatcherServlet en  doDispatch el método de programación, doDispatch el análisis del código fuente es el siguiente:

 

 A través del análisis del código fuente, se puede ver que el interceptor en Sping también se realiza a través de la idea de proxy dinámico y notificación envolvente.

1.6 Agregar un prefijo de acceso unificado

Agregue el prefijo api a todas las direcciones de solicitud, c significa todo

 

@Configuration
public class AppConfig implements WebMvcConfigurer {
    // 所有的接口添加 api 前缀
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        configurer.addPathPrefix("api", c -> true);
    }
}

2. Manejo unificado de excepciones

Agregar  @ControllerAdvice la clase de notificación del controlador a la clase actual

Agregue al método  @ExceptionHandler(xxx.class), indicando el controlador de excepciones, y agregue el código comercial devuelto por la excepción

 

@RestController
@RequestMapping("/user")
public class UserController {
    @RequestMapping("/index")
    public String index() {
        int num = 10/0;
        return "Hello Index";
    }
}

En el paquete de configuración, crea  MyExceptionAdvice la clase.

@RestControllerAdvice // 当前是针对 Controller 的通知类(增强类)
public class MyExceptionAdvice {
    @ExceptionHandler(ArithmeticException.class)
    public HashMap<String,Object> arithmeticExceptionAdvice(ArithmeticException e) {
        HashMap<String, Object> result = new HashMap<>();
        result.put("state",-1);
        result.put("data",null);
        result.put("msg" , "算出异常:"+ e.getMessage());
        return result;
    }
}

 

También puedes escribir así, el efecto es el mismo.

 

@ControllerAdvice
public class MyExceptionAdvice {
    @ExceptionHandler(ArithmeticException.class)
    @ResponseBody
    public HashMap<String,Object> arithmeticExceptionAdvice(ArithmeticException e) {
        HashMap<String, Object> result = new HashMap<>();
        result.put("state",-1);
        result.put("data",null);
        result.put("msg" , "算数异常:"+ e.getMessage());
        return result;
    }
}

Si hay otra excepción de puntero nulo, el código anterior no funcionará y se debe escribir un controlador para las excepciones de puntero nulo.

@ExceptionHandler(NullPointerException.class)
public HashMap<String,Object> nullPointerExceptionAdvice(NullPointerException e) {
    HashMap<String, Object> result = new HashMap<>();
    result.put("state",-1);
    result.put("data",null);
    result.put("msg" , "空指针异常异常:"+ e.getMessage());
    return result;
}
@RequestMapping("/index")
public String index(HttpServletRequest request,String username, String password) {
    Object obj = null;
    System.out.println(obj.hashCode());
    return "Hello Index";
}

Pero una cosa a tener en cuenta es que si cada excepción se escribe de esta manera, la carga de trabajo será muy grande y hay excepciones personalizadas, por lo que definitivamente no es bueno escribir de esta manera. Ya que es una excepción, simplemente escriba Excepción directamente. . Es la clase principal de todas las excepciones. Si encuentra dos excepciones que no están escritas anteriormente, coincidirá directamente con Exception

Cuando hay varias notificaciones de excepción, el orden de coincidencia es que la clase actual y sus subclases coincidan hacia arriba.

@ExceptionHandler(Exception.class)
public HashMap<String,Object> exceptionAdvice(Exception e) {
    HashMap<String, Object> result = new HashMap<>();
    result.put("state",-1);
    result.put("data",null);
    result.put("msg" , "异常:"+ e.getMessage());
    return result;
}

 

Puede ver que la coincidencia de prioridad sigue siendo la excepción de puntero nulo escrita anteriormente

3. Retorno de formato de datos unificado

3.1 Realización de la devolución del formato de datos unificado

(1) Agregar a la clase actual @ControllerAdvice

(2) Implementar  ResponseBodyAdvice reescribiendo su método

  • supports Método, este método indica si el contenido debe reescribirse (a través de este método, algunos controladores y métodos pueden reescribirse selectivamente), y si se reescribe, devuelve verdadero

  • beforeBodyWrite método, este método se llama antes de que el método regrese

 

@ControllerAdvice
public class MyResponseAdvice implements ResponseBodyAdvice {

    // 返回一个 boolean 值,true 表示返回数据之前对数据进行重写,也就是会进入 beforeBodyWrite 方法
    // 返回 false 表示对结果不进行任何处理,直接返回
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }

    // 方法返回之前调用此方法
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        HashMap<String,Object> result = new HashMap<>();
        result.put("state",1);
        result.put("data",body);
        result.put("msg","");
        return result;
    }
}
@RestController
@RequestMapping("/user")
public class UserController {

    @RequestMapping("/login")
    public boolean login(HttpServletRequest request,String username, String password) {
        boolean result = false;
        if (StringUtils.hasLength(username) && StringUtils.hasLength(password)) {
            if(username.equals("admin") && password.equals("admin")) {
                HttpSession session = request.getSession();
                session.setAttribute("userinfo","userinfo");
                return true;
            }
        }
        return result;
    }

    @RequestMapping("/reg")
    public int reg() {
        return 1;
    }
}

 

3.2 Análisis del código fuente de @ControllerAdvice

A través  @ControllerAdvice del análisis del código fuente, podemos conocer el proceso de ejecución de la excepción unificada anterior y la devolución de datos unificados.

(1) Primero mire el código fuente de @ControllerAdvice

 

Puede ver  @ControllerAdvice que se deriva del  @Component componente y toda la inicialización del componente llamará  InitializingBean a la interfaz

(2) Veamos qué clases de implementación tiene initializeBean

Durante el proceso de consulta, se encontró que la subclase de implementación en Spring MVC es que  RequestMappingHandlerAdapterhay un método en ella  afterPropertiesSet(), que indica el método que se ejecutará después de que se completen todas las configuraciones de parámetros.

 

 

(3) Y hay un método initControllerAdviceCache en este método, consulte este método

 

 

Se encuentra que este método buscará y usará todas  @ControllerAdvice las clases durante la ejecución. Cuando se envía un evento, se llamará al método Advice correspondiente, como llamar a la encapsulación de datos unificados antes de devolver los datos. Por ejemplo, cuando ocurre una excepción, se implementa llamando al método de asesoramiento anormal

Supongo que te gusta

Origin blog.csdn.net/zhangjiaming_zjm/article/details/130422871
Recomendado
Clasificación