Всем привет, я Мисти.
Сегодня поговорим о том, как разделить фронтенд и бэкенд режим разработки на базе SpringBoot.дружелюбноВозвращает единый стандартный формат и способ изящной обработки глобальных исключений.
Прежде всего, давайте разберемся, почему мы хотим вернуться к единому стандартному формату?
Зачем возвращать единый стандартный формат в SpringBoot
По умолчанию существует три распространенных формата возврата SpringBoot:
Первый: вернуть строку
@GetMapping("/hello")
public String getStr(){
return "hello,javadaily";
}
На данный момент возвращаемое значение, полученное при вызове интерфейса, выглядит следующим образом:
hello,javadaily
Второй: вернуть пользовательский объект
@GetMapping("/aniaml")
public Aniaml getAniaml(){
Aniaml aniaml = new Aniaml(1,"pig");
return aniaml;
}
На данный момент возвращаемое значение, полученное при вызове интерфейса, выглядит следующим образом:
{
"id": 1,
"name": "pig"
}
Третий тип: исключение интерфейса
@GetMapping("/error")
public int error(){
int i = 9/0;
return i;
}
На данный момент возвращаемое значение, полученное при вызове интерфейса, выглядит следующим образом:
{
"timestamp": "2021-07-08T08:05:15.423+00:00",
"status": 500,
"error": "Internal Server Error",
"path": "/wrong"
}
Основываясь на вышеуказанных ситуациях, если вы и интерфейс совместного разработчика интерфейса, они будут очень нажатия. Так как мы не дали ему единый формат, персонал переднего конца не знал, как обрабатывать возвращаемое значение.
Более того, некоторые студенты, такие как Сяо Чжан, любят инкапсулировать результат. Он использует объект «Результат», а Сяо Ван также любит упаковывать результат, но использует объект «Ответ». быть сумасшедшим.
Поэтому нам необходимо определить единый стандартный формат возврата в нашем проекте.
Определить стандартный формат возврата
Стандартный формат возврата содержит как минимум 3 части:
- status Значение состояния: код состояния различных возвращаемых результатов единообразно определяется серверной частью.
- Описание сообщения: описание результата этого вызова API
- data data: данные, возвращенные на этот раз.
{
"status":"100",
"message":"操作成功",
"data":"hello,javadaily"
}
Конечно, другие значения расширения также могут быть добавлены по мере необходимости, например, мы добавили время вызова интерфейса к возвращаемому объекту.
- timestamp: время вызова интерфейса
Определить возвращаемый объект
@Data
public class ResultData<T> {
/** 结果状态 ,具体状态码参见ResultData.java*/
private int status;
private String message;
private T data;
private long timestamp ;
public ResultData (){
this.timestamp = System.currentTimeMillis();
}
public static <T> ResultData<T> success(T data) {
ResultData<T> resultData = new ResultData<>();
resultData.setStatus(ReturnCode.RC100.getCode());
resultData.setMessage(ReturnCode.RC100.getMessage());
resultData.setData(data);
return resultData;
}
public static <T> ResultData<T> fail(int code, String message) {
ResultData<T> resultData = new ResultData<>();
resultData.setStatus(code);
resultData.setMessage(message);
return resultData;
}
}
определить код состояния
public enum ReturnCode {
/**操作成功**/
RC100(100,"操作成功"),
/**操作失败**/
RC999(999,"操作失败"),
/**服务限流**/
RC200(200,"服务开启限流保护,请稍后再试!"),
/**服务降级**/
RC201(201,"服务开启降级保护,请稍后再试!"),
/**热点参数限流**/
RC202(202,"热点参数限流,请稍后再试!"),
/**系统规则不满足**/
RC203(203,"系统规则不满足要求,请稍后再试!"),
/**授权规则不通过**/
RC204(204,"授权规则不通过,请稍后再试!"),
/**access_denied**/
RC403(403,"无访问权限,请联系管理员授予权限"),
/**access_denied**/
RC401(401,"匿名用户访问无权限资源时的异常"),
/**服务异常**/
RC500(500,"系统异常,请稍后重试"),
INVALID_TOKEN(2001,"访问令牌不合法"),
ACCESS_DENIED(2003,"没有权限访问该资源"),
CLIENT_AUTHENTICATION_FAILED(1001,"客户端认证失败"),
USERNAME_OR_PASSWORD_ERROR(1002,"用户名或密码错误"),
UNSUPPORTED_GRANT_TYPE(1003, "不支持的认证模式");
/**自定义状态码**/
private final int code;
/**自定义描述**/
private final String message;
ReturnCode(int code, String message){
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
}
Единый формат возврата
@GetMapping("/hello")
public ResultData<String> getStr(){
return ResultData.success("hello,javadaily");
}
На данный момент возвращаемое значение, полученное при вызове интерфейса, выглядит следующим образом:
{
"status": 100,
"message": "hello,javadaily",
"data": null,
"timestamp": 1625736481648,
"httpStatus": 0
}
Таким образом были достигнуты желаемые результаты, все, что я вижу во многих проектах этого письма, уровень контроллераResultData.success()
Оберните возвращенный результат и верните его во внешний интерфейс.
Увидев это, мы могли бы остановиться и подумать, а какие недостатки в этом?
Самым большим недостатком является то, что нам нужно вызывать каждый интерфейс, который мы пишем позже.ResultData.success()
Эта строка кода оборачивает результат, повторяет работу и тратит энергию; над ней легко могут посмеяться другие ветераны.
Так что нам нужно оптимизировать код, цель не в том, чтобы вручную формулировать каждый интерфейсResultData
возвращаемое значение.
Расширенная реализация
Оптимизировать этот код очень просто, нам нужно только использовать SpringBoot для обеспеченияResponseBodyAdvice
Вот и все.
Роль ResponseBodyAdvice: перехватывать возвращаемое значение метода Controller и единообразно обрабатывать возвращаемое значение/тело ответа.Обычно используется для унификации возвращаемого формата, шифрования и дешифрования, подписи и т. д.
Давайте взглянемResponseBodyAdvice
Исходный код:
public interface ResponseBodyAdvice<T> {
/**
* 是否支持advice功能
* true 支持,false 不支持
*/
boolean supports(MethodParameter var1, Class<? extends HttpMessageConverter<?>> var2);
/**
* 对返回的数据进行处理
*/
@Nullable
T beforeBodyWrite(@Nullable T var1, MethodParameter var2, MediaType var3, Class<? extends HttpMessageConverter<?>> var4, ServerHttpRequest var5, ServerHttpResponse var6);
}
Нам нужно только написать конкретный класс реализации
/**
* @author jam
* @date 2021/7/8 10:10 上午
*/
@RestControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice<Object> {
@Autowired
private ObjectMapper objectMapper;
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
return true;
}
@SneakyThrows
@Override
public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
if(o instanceof String){
return objectMapper.writeValueAsString(ResultData.success(o));
}
return ResultData.success(o);
}
}
Нужно обратить внимание на два места:
-
@RestControllerAdvice
аннотация@RestControllerAdvice
да@RestController
Улучшение аннотаций может выполнять три функции:- глобальная обработка исключений
- глобальная привязка данных
- предварительная обработка глобальных данных
-
Тип строки Суждение
if(o instanceof String){
return objectMapper.writeValueAsString(ResultData.success(o));
}
Этот код должен быть добавлен, если контроллер прямой возвратной строки, то SpringBoot возвращается напрямую, поэтому нам нужно вручную преобразовать в JSON.
После вышеописанной обработки нам больше не нужно проходитьResultData.success()
Приходите на конвертацию, возвращайте исходный формат данных напрямую, и SpringBoot автоматически помогает нам реализовать инкапсуляцию класса-обертки.
@GetMapping("/hello")
public String getStr(){
return "hello,javadaily";
}
На данный момент результатом данных, возвращаемых вызовом интерфейса, является:
{
"status": 100,
"message": "操作成功",
"data": "hello,javadaily",
"timestamp": 1626427373113
}
Вы чувствуете себя идеально, не волнуйтесь, вас ждет еще один вопрос.
Проблема исключения интерфейса
В настоящее время существует проблема, потому что у нас нет исключения для контроллера.Когда мы вызываем метод, как только возникает аномалия, возникают проблемы, такие как интерфейс ниже.
@GetMapping("/wrong")
public int error(){
int i = 9/0;
return i;
}
Возвращаемый результат:
Это явно не тот результат, который нам нужен, интерфейс сообщает об ошибке и возвращает код ответа об успешной операции, а интерфейс будет бить людей, когда они это увидят.
Не волнуйтесь, давайте перейдем ко второй теме, как изящно обрабатывать глобальные исключения.
Зачем SpringBoot нужен глобальный обработчик исключений
-
Не нужно писать try...catch, он равномерно перехватывается глобальным обработчиком исключений.
Самое большое удобство использования глобальных аномальных процессоров заключается в том, что программисту больше не нужно писать при написании кода.
try...catch
Что ж, как мы говорили ранее, по умолчанию результат, возвращенный Sprilboot, когда произойдет исключение, это:
{
"timestamp": "2021-07-08T08:05:15.423+00:00",
"status": 500,
"error": "Internal Server Error",
"path": "/wrong"
}
这种数据格式返回给前端,前端是看不懂的,所以这时候我们一般通过
try...catch
для обработки исключений
@GetMapping("/wrong")
public int error(){
int i;
try{
i = 9/0;
}catch (Exception e){
log.error("error:{}",e);
i = 0;
}
return i;
}
Цель, которую мы преследуем, это точно не нужно писать вручную сноваtry...catch
, но хотите, чтобы их обрабатывал глобальный обработчик исключений.
- Пользовательские исключения могут обрабатываться только глобальным обработчиком исключений.
@GetMapping("error1")
public void empty(){
throw new RuntimeException("自定义异常");
}
-
Когда мы вводим валидатор параметра Validator, если проверка параметра не пройдена, будет выдано исключение, которое в настоящее время недоступно.
try...catch
Обнаружено, можно использовать только глобальный обработчик исключений.Пожалуйста, обратитесь к этой статье для проверки параметров интеграции SpringBootКоды разработки SpringBoot — встроенная проверка параметров и расширенные навыки
Как реализовать глобальный обработчик исключений
@Slf4j
@RestControllerAdvice
public class RestExceptionHandler {
/**
* 默认全局异常处理。
* @param e the e
* @return ResultData
*/
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ResultData<String> exception(Exception e) {
log.error("全局异常信息 ex={}", e.getMessage(), e);
return ResultData.fail(ReturnCode.RC500.getCode(),e.getMessage());
}
}
Есть три детали, которые нужно объяснить:
-
@RestControllerAdvice
, расширенный класс RestController, который можно использовать для реализации глобального обработчика исключений. -
@ExceptionHandler
, Унифицированная обработка определенного типа исключений, благодаря чему снижается частота повторения кода и сложность, например, для получения пользовательских исключений можно@ExceptionHandler(BusinessException.class)
-
@ResponseStatus
Указывает код состояния http, полученный клиентом
Эффект опыта
В это время мы вызываем следующий интерфейс:
@GetMapping("error1")
public void empty(){
throw new RuntimeException("自定义异常");
}
Возвращаемые результаты следующие:
{
"status": 500,
"message": "自定义异常",
"data": null,
"timestamp": 1625795902556
}
В основном удовлетворяет наши потребности.
Но когда мы одновременно включаем функцию инкапсуляции единого стандартного форматаResponseAdvice
иRestExceptionHandler
Возникла новая проблема с глобальным обработчиком исключений:
{
"status": 100,
"message": "操作成功",
"data": {
"status": 500,
"message": "自定义异常",
"data": null,
"timestamp": 1625796167986
},
"timestamp": 1625796168008
}
Результат, возвращаемый в это время, выглядит следующим образом: функция улучшения унифицированного формата снова инкапсулирует возвращенный аномальный результат, поэтому нам нужно решить эту проблему дальше.
Стандартный формат, возвращаемый глобальным доступом к исключению
Сделать глобальное исключение доступом к стандартному формату очень просто, потому что глобальный обработчик исключений уже инкапсулировал за нас стандартный формат, и нам остается только вернуть его непосредственно клиенту.
@SneakyThrows
@Override
public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
if(o instanceof String){
return objectMapper.writeValueAsString(ResultData.success(o));
}
if(o instanceof ResultData){
return o;
}
return ResultData.success(o);
}
Ключевой код:
if(o instanceof ResultData){
return o;
}
Если возвращенный результат является объектом ResultData, просто верните его напрямую.
В это время мы снова вызываем вышеуказанный метод ошибки, и возвращаемый результат соответствует нашим требованиям.
{
"status": 500,
"message": "自定义异常",
"data": null,
"timestamp": 1625796580778
}
Что ж, сегодняшняя статья здесь.Я надеюсь, что с помощью этой статьи вы сможете освоить, как реализовать единый стандартный формат для возврата в вашем проекте и изящно обрабатывать глобальные исключения.
Исходный код старой серии птиц был загружен на GitHub, и вам необходимо ответить на ключевое слово в общедоступной учетной записи [JAVA Daily Knowledge Record]0923 Получать