[Spring Boot] Актуальный бой: реализация элегантного возврата данных

Практика: реализация элегантного возврата данных

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

1. Почему возвращаемое значение должно быть унифицировано

В процессе разработки проекта часто возникают вопросы взаимодействия данных в рамках системной архитектуры передачи данных серверного и клиентского интерфейсов или разделения фронта и тыла. Как обеспечить, чтобы данные были полными, четкими и простыми для понимания, является большой проблемой для разработчиков. Определение унифицированного формата возврата данных способствует повышению эффективности разработки, снижению затрат на связь и снижению затрат на разработку вызывающего объекта. В настоящее время более популярным является взаимодействие с данными на основе формата JSON. Но JSON — это только формат сообщения, и содержание данных в нем нужно переработать и определить. Независимо от того, является ли это интерфейсом HTTP или интерфейсом RPC, очень важно поддерживать единый формат возвращаемого значения.

В проекте мы будем инкапсулировать ответ в JSON и возвращать его.Как правило, формат данных всех интерфейсов будет унифицирован, чтобы интерфейс (iOS, Android, Web) работал с данными последовательно и легко. При нормальных обстоятельствах не существует фиксированной спецификации для унифицированного формата возвращаемых данных, если можно четко описать состояние возвращаемых данных и конкретные данные, которые должны быть возвращены, но обычно он включает три части: код состояния, подсказку сообщения и конкретные данные. данные. Например, базовый формат данных, возвращаемый общими системными требованиями, выглядит следующим образом:

	{
    
    
		"code":20000,
		"message": "成功",
		"data": {
    
    
			"item": [
				{
    
    
					"id":"1",
					"name":"ysxq",
					"intro":"备注"
				}
			]
		}
	}

Из приведенного выше примера мы знаем, что определенное возвращаемое значение содержит 4 элемента: результат ответа, код ответа, сообщение и возвращаемые данные.

2. Единый возврат данных

2.1 Определить формат данных

Определите основные элементы возвращаемого значения, чтобы убедиться, что фоновое выполнение возвращает эти поля независимо от успеха или неудачи, и никакие другие поля не появляются. Определенное возвращаемое значение включает в себя следующее:

  • Целочисленный код: возвращает 0 в случае успеха и возвращает определенный код ошибки в случае сбоя.
  • Строковое сообщение: возвращает null в случае успеха и возвращает конкретное сообщение об ошибке в случае сбоя.
  • Данные T: возвращает определенное значение в случае успеха и ноль в случае неудачи.

Согласно приведенному выше определению формата возвращаемых данных фактический шаблон возвращаемых данных выглядит следующим образом:

	{
    
    
		"code":20000,
		"message": "成功",
		"data": {
    
    
			"item": [
				{
    
    
					"id":"1",
					"name":"ysxq",
					"intro":"备注"
				}
			]
		}
	}

Среди них поле данных является универсальным полем, которое возвращает тип данных, требуемый внешним интерфейсом в соответствии с реальным бизнесом.

2.2 Определение кода состояния

В возвращаемых данных есть очень важное поле: код состояния. Поле кода состояния позволяет серверу и клиенту четко знать результат операции, успешно ли обработано дело, а в случае сбоя — причину сбоя и другую информацию. Поэтому очень важно определить четкие и понятные коды состояния. Коды состояния определяются, как показано в таблице.

вставьте сюда описание изображения
Приведенное выше определение является общим кодом состояния, и другие коды состояния, связанные с бизнесом, должны быть определены в соответствии с фактическим бизнесом.

2.3 Определить класс обработки данных

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

/**
 * @Title: JSONResult.java
 * @Package com.example.demo
 * @Description: 自定义响应数据结构
                200:表示成功
                500:表示错误,错误信息在msg字段中
                501: bean验证错误,无论多少个错误都以map形式返回
                502:拦截器拦截到用户token出错
                555:异常抛出信息
*/
public class JSONResult {
    
    
    //定义jackson对象
    private static final ObjectMapper MAPPER = new ObjectMapper();
    //响应业务状态
    private Integer status;
    //响应消息
    private String msg;
    //响应中的数据
    private Object data;

    public static JSONResult build(Integer status, String msg, Object data) {
    
    
        return new JSONResult(status, msg, data);
    }
    public static JSONResult ok(Object data) {
    
    
        return new JSONResult(data);
    }
    public static JSONResult ok() {
    
    
        return new JSONResult(null);
    }
    public static JSONResult errorMsg(String msg) {
    
    
        return new JSONResult(500, msg, null);
    }
    public static JSONResult errorMap(Object data) {
    
    
        return new JSONResult(501, "error", data);
    }
    public static JSONResult errorTokenMsg(String msg) {
    
    
        return new JSONResult(502, msg, null);
    }
    public static JSONResult errorException(String msg) {
    
    
        return new JSONResult(555, msg, null);
    }

    public JSONResult() {
    
    

    }

    public JSONResult (Integer status, String msg, Object data) {
    
    
        this.status = status;
        this.msg = msg;
        this.data = data;
    }

    public JSONResult(Object data) {
    
    
        this.status = 200;
        this.msg = "OK";
        this.data = data;
    }
    public Boolean isOK() {
    
    
        return this.status == 200;
    }

/**
 * @Description: 将json结果集转化为JSONResult对象,需要转换的对象是一个类
 * @param jsonData
 * @param clazz
 * @return
*/
    public static JSONResult formatToPojo(String jsonData, Class<?> clazz) {
    
    
        try {
    
    
            if (clazz == null) {
    
    
                return MAPPER.readValue(jsonData, JSONResult.class);
            }
            JsonNode jsonNode = MAPPER.readTree(jsonData);
            JsonNode data = jsonNode.get("data");
            Object obj = null;
            if (clazz != null) {
    
    
                if (data.isObject()) {
    
    
                    obj = MAPPER.readValue(data.traverse(), clazz);
                }else if (data.isTextual()) {
    
    
                    obj = MAPPER.readValue(data.asText(), clazz);
                }
            }
            return build(jsonNode.get("status").intValue(), jsonNode.get("msg").asText(), obj);
        } catch (Exception e) {
    
    
            return null;
        }
    }

/**
 *
 * @Description: 没有object对象的转换
 * @param json
 * @return
*/

public static JSONResult format(String json) {
    
    
    try {
    
    
        return MAPPER.readValue(json, JSONResult.class);
    } catch (Exception e) {
    
    
        e.printStackTrace();
    }
    return null;
}

/**
 *
 * @Description: Object是集合转换,需要转换的对象是一个list
 * @param jsonData
 * @param clazz
 * @return
 */
public static JSONResult formatToList(String jsonData, Class<?> clazz) {
    
    
    try {
    
    
        JsonNode jsonNode = MAPPER.readTree(jsonData);
        JsonNode data = jsonNode.get("data");
        Object obj = null;
        if (data.isArray() && data.size() > 0) {
    
    
            obj = MAPPER.readValue(data.traverse(), MAPPER.getTypeFactory().constructCollectionType(List.class, clazz));
        }
        return build(jsonNode.get("status").intValue(), jsonNode.get("msg").asText(), obj);
    } catch (Exception e) {
    
    
        return null;
    }
}
    public String getOK() {
    
    
        return ok;
    }
    public void setOK(String ok) {
    
    
        this.ok = ok;
    }

}

Класс обработки возврата данных определен выше, и определена структура данных ответа. Возврат данных всех интерфейсов единообразно обрабатывается посредством этого типа обработки. После получения таких данных необходимо использовать метод этого класса для преобразования их в соответствующий формат типа данных (класс или список).

2.4 Обработка возврата данных

После определения класса обработки данных добавьте обработку данных к возвращаемым данным в контроллере. Вызов выглядит следующим образом:

 	@RequestMapping("/getUser")
    public JSONResult getUserJson() {
    
    
        User u = new User();
        u.setName( "ysxq");
        u.setAge(20);
        u.setBirthday(new Date());
        u.setPassword("ysxq123456");
        return JSONResult.ok(u);
    }

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

Запустите проект helloworld, зайдите на него в браузере http://localhost:8080/user/getUser, и данные страницы вернутся следующим образом:

	{
    
    
		"code":200,
		"message": "OK",
		"data": {
    
    
			"name":"ysxq",
			"age": 20,
			"birthday": "2000-12-18 18:17:15"
		}
	}

Возвращенные данные результата могут быть возвращены в соответствии с нашим ожидаемым форматом результата при нормальных условиях.

3. Глобальная обработка исключений

В процессе разработки проекта обязательно будут возникать нештатные ситуации, как быть с нештатными ситуациями, и как сделать так, чтобы программа корректно возвращала данные при возникновении нештатных ситуаций? Нельзя ли добавить все методы с помощью try catch? Далее мы расскажем, как Spring Boot обрабатывает глобальные исключения и как возвращать данные в унифицированном формате после перехвата исключений.

3.1 Реализация глобальной обработки исключений

Перед введением нам нужно понять общие методы обработки исключений в Spring. В общем, существует много способов обработки исключений в среде Spring Boot, включая глобальный захват исключений и локальный захват исключений. Вот еще три часто используемых решения для обработки исключений.

(1) Используйте @ExceptionHandler для обработки локальных исключений.

Обработка исключений реализована в контроллере путем добавления метода с аннотацией @ExceptionHandler. Этот метод очень прост в реализации, но он может обрабатывать только исключения контроллера, аннотированные с помощью @ExceptionHandler, но не может обрабатывать исключения от других контроллеров, поэтому использовать его не рекомендуется.

(2) Настройте класс SimpleMappingExceptionResolver для обработки исключений.

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

(3) Используйте аннотацию ControllerAdvice для обработки глобальных исключений.

Используйте аннотации @ControllerAdvice и @ExceptionHandler для реализации глобальной обработки исключений. @ControllerAdvice определяет класс глобальной обработки исключений, а @ExceptionHandler указывает тип исключения, перехватываемого настраиваемым методом обработки ошибок. Реализуйте глобальный захват исключений и выполните специальную обработку определенных исключений.

Приведенные выше три решения могут обеспечить глобальную обработку исключений. Однако рекомендуется использовать аннотацию @ControllerAdvice для обработки глобальных исключений, чтобы разные исключения можно было обрабатывать отдельно.

3.2 Используйте аннотацию @ControllerAdvice для реализации глобальной обработки исключений

В следующем примере демонстрируется использование аннотации @ControllerAdvice для достижения глобальной унифицированной обработки исключений. Определите собственный класс обработки исключений GlobalExceptionHandler, конкретный пример кода выглядит следующим образом:

@ControllerAdvice
public class GlobalExceptionHandler {
    
    
    public static final String ERROR_VIEW = "error";
    Logger logger = LoggerFactory.getLogger(getClass());
    @ExceptionHandler(value = {
    
    Exception.class})
    public Object errorHandler(HttpServletRequest request, HttpServletResponse response, Exception e) throws Exception {
    
    
        //e.printStackTrace();
        //记录日志
        logger.error(ExceptionUtils.getMessage(e));
        //是否Ajax请求
        if (isAjax(request)) {
    
    
            return JSONResult.errorException(e.getMessage());
        } else {
    
    
            ModelAndView mav = new ModelAndView();
            mav.addObject("exception", e);
            mav.addObject("url", request.getRequestURL());
            mav.setViewName(ERROR_VIEW);
            return mav;
        }
    }
    /**
     * @Title: GlobalExceptionHandler.java
     * @Package com.example.demo
     * @Description: 判断是否是Aiax请求
     */
    public static boolean isAjax(HttpServletRequest httprequest) {
    
    
        return (httprequest.getHeader("X-Requested-With") !=null && "XMLHttpRequest".equals(httprequest.getHeader("X-Requested-With")));
    }
}

Приведенный выше пример обрабатывает все исключения.Если вам нужно обработать другие исключения, такие как NullPointerException, вам нужно только использовать аннотацию @ExceptionHandler(value = {NullPointerException.class}) в классе GlobalException, чтобы переопределить метод обработки исключений.

Запустите проект, введите в браузере http://localhost:8088/err/error, и результат показан на рисунке.
вставьте сюда описание изображения

После обработки исключения страница автоматически настраивается на унифицированную страницу ошибок.Если возникает ошибка запроса Ajax, данные будут возвращены единообразно в соответствии с определенным форматом данных JSON.

Supongo que te gusta

Origin blog.csdn.net/weixin_45627039/article/details/131964634
Recomendado
Clasificación