深圳 营销型网站公司,盘锦网站制作,三明市网站建设,电商个人网站建设做前后端分离的项目#xff0c;为了方便前端处理数据#xff0c;都会将返回的数据封装到统一的结构下#xff0c;这样前端拿到数据可以根据指定的字段做不同的业务逻辑处理。
1、异常信息统一拦截
项目开发中#xff0c;难免会发生异常#xff0c;如果不做拦截#xff…做前后端分离的项目为了方便前端处理数据都会将返回的数据封装到统一的结构下这样前端拿到数据可以根据指定的字段做不同的业务逻辑处理。
1、异常信息统一拦截
项目开发中难免会发生异常如果不做拦截当项目发生异常时会把异常的堆栈信息返回给前端这样不仅对前端没有意义而且会把服务器的相关信息暴露给外部用户造成信息的泄露在springboot中可以通过 RestControllerAdvice 注解实现对异常信息的拦截处理拦截到异常后给前端返回友好的提示实现逻辑如下
定义一个异常信息拦截类接口调用中抛出的异常都会被拦截到拦截到的异常会进入不同的处理方法将处理后的数据返回给请求端
import org.example.pojo.ApiResult;
import org.example.pojo.StatusCode;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.http.HttpStatus;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.UnsatisfiedServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.NoHandlerFoundException;import java.util.List;
import java.util.StringJoiner;/*** 统一异常处理** Author xingo* Date 2023/12/7*/
RestControllerAdvice
ConditionalOnWebApplication
public class RestControllerExceptionHandler {/*** 自定义异常处理*/ExceptionHandler(BusinessException.class)public ApiResult businessExceptionHandler(BusinessException e) {return ApiResult.fail(e.getCode(), e.getMessage());}/*** 参数错误异常处理*/ResponseStatus(HttpStatus.BAD_REQUEST)ExceptionHandler(IllegalArgumentException.class)public ApiResult illegalArgumentExceptionHandler(IllegalArgumentException e) {e.printStackTrace();return ApiResult.fail(StatusCode.C_10004);}/*** 参数校验异常处理*/ExceptionHandler(BindException.class)public ApiResult bindExceptionHandler(BindException e) {ListFieldError errors e.getBindingResult().getFieldErrors();if(errors ! null !errors.isEmpty()) {return ApiResult.fail(StatusCode.C_10004).setMessage(errors.get(0).getDefaultMessage());}return ApiResult.fail(StatusCode.C_10004);}/*** 参数校验异常处理*/ExceptionHandler(MethodArgumentNotValidException.class)public ApiResult methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {ListFieldError errors e.getBindingResult().getFieldErrors();StringBuilder builder new StringBuilder();if(null ! errors) {for(FieldError error : errors) {builder.append(,).append(error.getField()).append(:).append(error.getDefaultMessage());}return ApiResult.fail(HttpStatus.BAD_REQUEST.value(), 参数校验失败-[ builder.substring(1) ]);}return ApiResult.fail(StatusCode.C_10004);}/*** 不支持的请求方法*/ExceptionHandler(HttpRequestMethodNotSupportedException.class)public ApiResultObject httpRequestMethodNotSupportedExceptionHandler(HttpRequestMethodNotSupportedException e) {e.printStackTrace();return ApiResult.fail(HttpStatus.METHOD_NOT_ALLOWED.value(), 不支持的请求方法);}/*** 请求参数缺失*/ExceptionHandler(MissingServletRequestParameterException.class)public ApiResultObject missingServletRequestParameterExceptionHandler(MissingServletRequestParameterException e) {e.printStackTrace();return ApiResult.fail(HttpStatus.BAD_REQUEST.value(), 请求参数 e.getParameterName() 缺失数据类型 e.getParameterType());}/*** 请求参数类型错误*/ExceptionHandler(MethodArgumentTypeMismatchException.class)public ApiResultObject methodArgumentTypeMismatchExceptionHandler(MethodArgumentTypeMismatchException e) {e.printStackTrace();return ApiResult.fail(HttpStatus.BAD_REQUEST.value(), 请求参数类型错误);}/*** 请求地址不存在*/ExceptionHandler(NoHandlerFoundException.class)public ApiResultObject handleNoFoundException(NoHandlerFoundException e) {e.printStackTrace();return ApiResult.fail(HttpStatus.NOT_FOUND.value(), 请求地址不存在);}/*** 必须的请求参数不能为空*/ExceptionHandler(MissingServletRequestPartException.class)public ApiResultObject missingServletRequestPartExceptionHandler(MissingServletRequestPartException e) {e.printStackTrace();return ApiResult.fail(HttpStatus.BAD_REQUEST.value(), 必须的请求参数 e.getRequestPartName() 不能为空);}/*** 400 - Bad Request*/ResponseStatus(HttpStatus.BAD_REQUEST)ExceptionHandler(UnsatisfiedServletRequestParameterException.class)public ApiResultObject unsatisfiedServletRequestParameterExceptionHandler(UnsatisfiedServletRequestParameterException e) {String conditions StringUtils.arrayToDelimitedString(e.getParamConditions(), ,);StringJoiner params new StringJoiner(,);e.getActualParams().forEach((k, v) - params.add(k.concat().concat(ObjectUtils.nullSafeToString(v))));e.printStackTrace();return ApiResult.fail(HttpStatus.BAD_REQUEST.value(), 请求参数异常 conditions | params);}/*** 未捕获的异常* param e* return*/ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)ExceptionHandler(Exception.class)public ApiResult exceptionHandler(Exception e) {e.printStackTrace();return ApiResult.fail(StatusCode.C_ERROR);}
}自定义了一个异常类当业务逻辑处理时不符合要求可以抛出下面的异常
import org.example.pojo.StatusCode;/*** 自定义业务异常** Author xingo* Date 2023/12/7*/
public class BusinessException extends RuntimeException {private int code;public BusinessException(int code, String message) {super(message);this.code code;}public static BusinessException fail(int code, String message) {return new BusinessException(code, message);}public static BusinessException fail(StatusCode statusCode) {return new BusinessException(statusCode.getCode(), statusCode.getMessage());}public int getCode() {return code;}
}接口统一返回的数据实体类它的定义如下
import java.io.Serializable;/*** Author xingo* Date 2023/10/27*/
public class ApiResultT implements Serializable {/*** 响应状态码200-成功其他-失败*/private int code;/*** 响应数据*/private T data;/*** 响应结果描述*/private String message ;/*** 响应耗时毫秒*/private long time;public ApiResult() {}public ApiResult(T data) {this.data data;}public ApiResult(int code, T data, String message) {this.code code;this.data data;this.message message;}public int getCode() {return code;}public ApiResult setCode(int code) {this.code code;return this;}public String getMessage() {return message;}public ApiResult setMessage(String message) {this.message message;return this;}public T getData() {return data;}public ApiResult setData(T data) {this.data data;return this;}public long getTime() {return this.time;}public ApiResult setTime(long time) {this.time time;return this;}/*** 成功** return*/public static ApiResult success() {ApiResult result new ApiResult(StatusCode.C_200.getCode(), null, StatusCode.C_200.getMessage());return result;}/*** 成功** param data* param T* return*/public static T ApiResult success(T data) {ApiResult result new ApiResult(StatusCode.C_200.getCode(), data, StatusCode.C_200.getMessage());return result;}/*** 失败** param statusCode* return*/public static ApiResult fail(StatusCode statusCode) {return new ApiResult().setCode(statusCode.getCode()).setMessage(statusCode.getMessage());}/*** 失败** param code* param message* return*/public static ApiResult fail(int code, String message) {return new ApiResult().setCode(code).setMessage(message);}/*** 异常** return*/public static ApiResult error() {return new ApiResult().setCode(StatusCode.C_ERROR.getCode()).setMessage(StatusCode.C_ERROR.getMessage());}/*** 判断响应是否为成功响应** return*/public boolean isSuccess() {if (this.code ! 200) {return false;}return true;}public static ApiResult copyCodeAndMessage(ApiResult result) {return new ApiResult().setCode(result.getCode()).setMessage(result.getMessage());}
}/*** 状态枚举*/
public enum StatusCode {/*** 正常*/C_200(200, success),/*** 系统繁忙*/C_ERROR(-1, 系统繁忙),/*** 特殊错误信息*/C_10000(10000, 特殊错误信息),/*** 用户未登录*/C_10001(10001, 用户未登录),/*** 用户无访问权限*/C_10002(10002, 用户无访问权限),/*** 用户身份验证失败*/C_10003(10003, 用户身份验证失败),/*** 请求参数错误*/C_10004(10004, 请求参数错误),/*** 请求信息不存在*/C_10005(10005, 请求信息不存在),/*** 更新数据失败*/C_10006(10006, 更新数据失败),;private Integer code;private String message;StatusCode(int code, String message) {this.code code;this.message message;}public Integer getCode() {return code;}public String getMessage() {return message;}public static StatusCode getByCode(int code) {StatusCode[] values StatusCode.values();for (StatusCode value : values) {if (code value.code) {return value;}}return StatusCode.C_ERROR;}
}写一个测试接口在方法中抛出异常会发现返回的信息不是异常的堆栈内容而是一个友好的提示内容
import org.example.handler.BusinessException;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;/*** Author xingo* Date 2023/12/7*/
RestController
public class DemoController {GetMapping(/demo1)public Object demo1() {int i 1, j 0;return i / j;}GetMapping(/demo2)public Object demo2() {if(System.currentTimeMillis() 1) {throw BusinessException.fail(88888, 业务数据不合法);}return System.currentTimeMillis();}
}2、返回统一数据结构
如果接口返回的数据格式不统一接口请求端处理数据就会非常麻烦在接口交互中可以定义好一个数据结构接口服务端和请求端根据这个结构封装数据这样处理数据就会变得容易假设现在统一的数据结构是ApiResult这个结构规定code200时表示成功其他的code都是失败并且定义好code的值表示的失败含义。要达到统一数据结构返回需要借助springboot中的ResponseBodyAdvice接口它的主要作用是拦截Controller方法的返回值统一处理返回值/响应体一般用来统一返回格式加解密签名等等。只要借助这个接口里面的两个方法supports()和beforeBodyWrite()就可以实现我们想要的功能
import org.example.pojo.ApiResult;
import org.springframework.core.MethodParameter;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;import java.lang.annotation.Annotation;
import java.util.Objects;/*** Author xingo* Date 2023/12/7*/
RestControllerAdvice
public class ApiResultResponseBodyAdvice implements ResponseBodyAdviceObject, Ordered {private static final Class? extends Annotation ANNOTATION_TYPE ResponseBody.class;/*** 判断类或者方法是否使用了 ResponseBody 注解*/Overridepublic boolean supports(MethodParameter returnType, Class? extends HttpMessageConverter? converterType) {return !ApiResult.class.isAssignableFrom(returnType.getParameterType()) (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ANNOTATION_TYPE)|| returnType.hasMethodAnnotation(ANNOTATION_TYPE));}/*** 当写入body之前调用这个方法*/Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,Class? extends HttpMessageConverter? selectedConverterType,ServerHttpRequest request, ServerHttpResponse response) {// String类型的返回值要单独处理否则会报错将数据写入data字段然后再序列化为json字符串Class? returnClass returnType.getMethod().getReturnType();if(body instanceof String || Objects.equals(returnClass, String.class)) {return JacksonUtils.toJSONString(ApiResult.success(body));}// 已经是目标数据类型不处理if(body instanceof ApiResult) {return body;}// 封装统一对象return ApiResult.success(body);}Overridepublic int getOrder() {return Integer.MIN_VALUE 1;}
}这里面用到了json序列化使用jackson封装了一个json处理类对实体类进行序列化操作
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;/*** Author xingo* Date 2023/12/7*/
public class JacksonUtils {private static final ObjectMapper mapper new ObjectMapper();static {JavaTimeModule module new JavaTimeModule();// 序列化时对Long类型进行处理避免前端js处理数据时精度缺失module.addSerializer(Long.class, ToStringSerializer.instance);module.addSerializer(Long.TYPE, ToStringSerializer.instance);// java8日期处理module.addSerializer(LocalDateTime.class,new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss)));module.addSerializer(LocalDate.class,new LocalDateSerializer(DateTimeFormatter.ofPattern(yyyy-MM-dd)));module.addSerializer(LocalTime.class,new LocalTimeSerializer(DateTimeFormatter.ofPattern(HH:mm:ss)));module.addDeserializer(LocalDateTime.class,new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss)));module.addDeserializer(LocalDate.class,new LocalDateDeserializer(DateTimeFormatter.ofPattern(yyyy-MM-dd)));module.addDeserializer(LocalTime.class,new LocalTimeDeserializer(DateTimeFormatter.ofPattern(HH:mm:ss)));mapper.registerModules(module);// 反序列化的时候如果多了其他属性,不抛出异常mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);// 如果是空对象的时候,不抛异常mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);// 空对象不序列化mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);// 日期格式化mapper.setDateFormat(new SimpleDateFormat(yyyy-MM-dd HH:mm:ss));// 设置时区mapper.setTimeZone(TimeZone.getTimeZone(GMT8));// 驼峰转下划线
// mapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);// 语言mapper.setLocale(Locale.SIMPLIFIED_CHINESE);}/*** 反序列化* param json json字符串* param clazz 发序列化类型* return* param T*/public static T T parseObject(String json, ClassT clazz) {try {return mapper.readValue(json, clazz);} catch (JsonProcessingException e) {e.printStackTrace();}return null;}/*** 反序列化列表* param json* return* param T*/public static T ListT parseArray(String json) {try {TypeReferenceListT type new TypeReferenceListT(){};return mapper.readValue(json, type);} catch (JsonProcessingException e) {e.printStackTrace();}return null;}/*** 写为json串* param obj 对象* return*/public static String toJSONString(Object obj) {try {return mapper.writeValueAsString(obj);} catch (JsonProcessingException e) {e.printStackTrace();}return null;}
}通过上面的封装处理只要接口返回的数据不是ApiResult类型都会封装成该类型返回达到统一数据类型的目的。