Глобальная аномальная обработка Spring Cloud Gateway

задняя часть внешний интерфейс Spring

Глобальная обработка исключений в Spring Cloud Gateway не может быть обработана напрямую с помощью @ControllerAdvice, путем отслеживания выдачи информации об исключении, поиска соответствующего исходного кода и настройки некоторой логики обработки в соответствии с потребностями бизнеса.

Все шлюзы являются прокси-форвардингом для интерфейса, серверная часть соответствует REST API, а формат возвращаемых данных — JSON. Если обработка не выполняется, когда возникает исключение, сообщение об ошибке по умолчанию, выдаваемое шлюзом, представляет собой страницу, которая неудобна для внешнего интерфейса для обработки исключений.

Информация об исключении должна быть обработана, а данные в формате JSON возвращены клиенту. Давайте сначала посмотрим на код реализации, а потом расскажем о местах, на которые нужно обратить внимание.

Пользовательская логика обработки исключений:

package com.cxytiandi.gateway.exception;

import java.util.HashMap;
import java.util.Map;

import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpStatus;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;

/**
 * 自定义异常处理
 * 
 * <p>异常时用JSON代替HTML异常信息<p>
 * 
 * @author yinjihuan
 *
 */
public class JsonExceptionHandler extends DefaultErrorWebExceptionHandler {

	public JsonExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties,
			ErrorProperties errorProperties, ApplicationContext applicationContext) {
		super(errorAttributes, resourceProperties, errorProperties, applicationContext);
	}

	/**
	 * 获取异常属性
	 */
	@Override
	protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
		int code = 500;
		Throwable error = super.getError(request);
		if (error instanceof org.springframework.cloud.gateway.support.NotFoundException) {
			code = 404;
		}
		return response(code, this.buildMessage(request, error));
	}

	/**
	 * 指定响应处理方法为JSON处理的方法
	 * @param errorAttributes
	 */
	@Override
	protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
		return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
	}

	/**
	 * 根据code获取对应的HttpStatus
	 * @param errorAttributes
	 */
	@Override
	protected HttpStatus getHttpStatus(Map<String, Object> errorAttributes) {
		int statusCode = (int) errorAttributes.get("code");
		return HttpStatus.valueOf(statusCode);
	}

	/**
	 * 构建异常信息
	 * @param request
	 * @param ex
	 * @return
	 */
	private String buildMessage(ServerRequest request, Throwable ex) {
		StringBuilder message = new StringBuilder("Failed to handle request [");
		message.append(request.methodName());
		message.append(" ");
		message.append(request.uri());
		message.append("]");
		if (ex != null) {
			message.append(": ");
			message.append(ex.getMessage());
		}
		return message.toString();
	}

	/**
	 * 构建返回的JSON数据格式
	 * @param status		状态码
	 * @param errorMessage  异常信息
	 * @return
	 */
	public static Map<String, Object> response(int status, String errorMessage) {
		Map<String, Object> map = new HashMap<>();
		map.put("code", status);
		map.put("message", errorMessage);
		map.put("data", null);
		return map;
	}

}

Переопределите конфигурацию по умолчанию:

package com.cxytiandi.gateway.exception;

import java.util.Collections;
import java.util.List;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.result.view.ViewResolver;

/**
 * 覆盖默认的异常处理
 * 
 * @author yinjihuan
 *
 */
@Configuration
@EnableConfigurationProperties({ServerProperties.class, ResourceProperties.class})
public class ErrorHandlerConfiguration {

    private final ServerProperties serverProperties;

    private final ApplicationContext applicationContext;

    private final ResourceProperties resourceProperties;

    private final List<ViewResolver> viewResolvers;

    private final ServerCodecConfigurer serverCodecConfigurer;

    public ErrorHandlerConfiguration(ServerProperties serverProperties,
                                     ResourceProperties resourceProperties,
                                     ObjectProvider<List<ViewResolver>> viewResolversProvider,
                                     ServerCodecConfigurer serverCodecConfigurer,
                                     ApplicationContext applicationContext) {
        this.serverProperties = serverProperties;
        this.applicationContext = applicationContext;
        this.resourceProperties = resourceProperties;
        this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes) {
        JsonExceptionHandler exceptionHandler = new JsonExceptionHandler(
                errorAttributes, 
                this.resourceProperties,
                this.serverProperties.getError(), 
                this.applicationContext);
        exceptionHandler.setViewResolvers(this.viewResolvers);
        exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());
        exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());
        return exceptionHandler;
    }
    
}

будь осторожен

  • Как вы возвращаете JSON вместо HTML?

В методе org.springframework.boot.autoconfigure.web.reactive.error.default () в методе getroutingFunction () в формате для управления форматом возврата исходный код выглядит следующим образом:

@Override
protected RouterFunction<ServerResponse> getRoutingFunction(
			ErrorAttributes errorAttributes) {
       return RouterFunctions.route(acceptsTextHtml(), this::renderErrorView)
				.andRoute(RequestPredicates.all(), this::renderErrorResponse);
}

Приоритетом здесь является использование HTML для отображения, если вы хотите использовать JSON, вы можете изменить его следующим образом:

protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
	   return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
}
  • getHttpStatus нужно переписать

Оригинальный метод заключается в получении соответствующего HttpStatus через статус Код выглядит следующим образом:

protected HttpStatus getHttpStatus(Map<String, Object> errorAttributes) {
	  int statusCode = (int) errorAttributes.get("status");
	  return HttpStatus.valueOf(statusCode);
}

Если в определяемом нами формате нет поля состояния, то будет сообщено об ошибке, и соответствующий код ответа не может быть найден. Либо добавьте подраздел состояния в возвращаемый формат данных, либо перепишите его. Здесь я возвращаю код, поэтому мне нужно переписать его. Код показан ниже:

@Override
protected HttpStatus getHttpStatus(Map<String, Object> errorAttributes) {
	int statusCode = (int) errorAttributes.get("code");
	return HttpStatus.valueOf(statusCode);
}

猿天地