SpringBoot系列—统一功能处理(统一数据返回格式)

发布于:2025-07-15 ⋅ 阅读:(14) ⋅ 点赞:(0)

上篇文章:

SpringBoot系列—统一功能处理(拦截器)https://blog.csdn.net/sniper_fandc/article/details/148997385?fromshare=blogdetail&sharetype=blogdetail&sharerId=148997385&sharerefer=PC&sharesource=sniper_fandc&sharefrom=from_link

目录

1 数据返回格式

2 统一数据返回格式


1 数据返回格式

        前后端分离的开发模式下,如果后端各种接口返回的数据不一致,就会导致前端开发很困难。并且如果后端返回的数据需要扩展,直接返回基本类型就会导致修改时很麻烦。因此通常后端返回自定义数据格式:

public enum ResultStatus {

    SUCCESS(200),

    UNLOGIN(-1),

    FAIL(-2);

    private Integer code;



    ResultStatus(int code) {

        this.code = code;

    }



    public Integer getCode() {

        return code;

    }



    public void setCode(Integer code) {

        this.code = code;

    }

}

@Data

public class Result {

    private ResultStatus status;

    private String errorMessage;

    private Object data;



    /**

     * 业务执行成功时返回的方法

     *

     * @param data

     * @return

     */

    public static Result success(Object data) {

        Result result = new Result();

        result.setStatus(ResultStatus.SUCCESS);

        result.setErrorMessage("");

        result.setData(data);

        return result;

    }



    /**

     * 业务执行失败时返回的方法

     *

     * @param

     * @return

     */

    public static Result fail(String msg) {

        Result result = new Result();

        result.setStatus(ResultStatus.FAIL);

        result.setErrorMessage(msg);

        result.setData("");

        return result;

    }



    /**

     * 用户未登录时返回的方法

     *

     * @param

     * @return

     */

    public static Result unlogin() {

        Result result = new Result();

        result.setStatus(ResultStatus.UNLOGIN);

        result.setErrorMessage("用户未登录");

        result.setData(null);

        return result;

    }

}

        通过Result类来统一对接口返回的数据进行包装,比如登录接口返回:

    @RequestMapping("/login")

    public Result login(String username, String password, HttpSession session) {

        //账号或密码为空

        if (!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {

            return Result.fail("账号或密码为空!");

        }

        //模拟验证数据, 账号密码正确

        if ("admin".equals(username) && "123456".equals(password)) {

            session.setAttribute("userName", username);

            return Result.success(true);

        }

        //账号密码错误

        return Result.fail("账号或密码错误!");

    }

2 统一数据返回格式

        但是实际中,每个方法返回的数据很灵活,有可能这个方法使用的Result来封装数据,但是其他方法可能没有使用Result封装数据,那我们统一数据返回格式的目的就没有达到。SpringBoot的统一数据返回格式使用方法如下:

@ControllerAdvice

public class ResponseAdvice implements ResponseBodyAdvice {

    @Autowired

    public ObjectMapper objectMapper;

    @Override

    public boolean supports(MethodParameter returnType, Class converterType) {

        return true;

    }



    @SneakyThrows

    @Override

    public Object beforeBodyWrite(Object body, MethodParameter returnType,

                                  MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest

                                          request, ServerHttpResponse response) {

        // 防止数据过度封装(接口中的某些方法可能已经使用了Result类来封装数据)

        if(body instanceof Result){

            return body;

        }

        // 如果返回的是String类型,使用Result来封装会报错

        if(body instanceof String){

            return objectMapper.writeValueAsString(Result.success(body));

        }

        return Result.success(body);

    }

}

        定义的统一数据返回格式的类必须实现ResponseBodyAdvice接口,同时加上@ControllerAdvice注解(@Component的子类)来交给Spring管理。

        supports()方法:判断是否要执行beforeBodyWrite方法,true为执行,false不执行。通过该方法可以选择哪些类或哪些方法的response要进行处理,其他的不进行处理。

        beforeBodyWrite方法:对response方法进行具体操作处理。@SneakyThrows注解可以统一处理方法中的异常。

        注意:如果返回的是String类型,使用Result来封装会报错。原因是SpringMVC默认会注册一些自带的HttpMessageConverter(消息转换器,理解为用来转换数据格式的),从先后顺序排列分别为ByteArrayHttpMessageConverter、StringHttpMessageConverter、SourceHttpMessageConverter、AllEncompassingFormHttpMessageConverter(内部根据依赖的包添加其他类型的转换器,有json格式的转化器)。下述为RequestMappingHandlerAdapter类的构造方法:

    public RequestMappingHandlerAdapter() {

        this.messageConverters.add(new ByteArrayHttpMessageConverter());

        this.messageConverters.add(new StringHttpMessageConverter());

        if (!shouldIgnoreXml) {

            try {

                this.messageConverters.add(new SourceHttpMessageConverter());

            } catch (Error var2) {

            }

        }



        this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());

    }

        当返回类型是String类型时,会先匹配到StringHttpMessageConverter转化器;如果引入了json的依赖,就会匹配json格式的转化器。下述为AllEncompassingFormHttpMessageConverter类的构造方法:

    public AllEncompassingFormHttpMessageConverter() {

        if (!shouldIgnoreXml) {

            try {

                this.addPartConverter(new SourceHttpMessageConverter());

            } catch (Error var2) {

            }



            if (jaxb2Present && !jackson2XmlPresent) {

                this.addPartConverter(new Jaxb2RootElementHttpMessageConverter());

            }

        }



        if (kotlinSerializationJsonPresent) {

            this.addPartConverter(new KotlinSerializationJsonHttpMessageConverter());

        }



        if (jackson2Present) {

            this.addPartConverter(new MappingJackson2HttpMessageConverter());

        } else if (gsonPresent) {

            this.addPartConverter(new GsonHttpMessageConverter());

        } else if (jsonbPresent) {

            this.addPartConverter(new JsonbHttpMessageConverter());

        }



        if (jackson2XmlPresent && !shouldIgnoreXml) {

            this.addPartConverter(new MappingJackson2XmlHttpMessageConverter());

        }



        if (jackson2SmilePresent) {

            this.addPartConverter(new MappingJackson2SmileHttpMessageConverter());

        }



    }

        在StringHttpMessageConverter转化器中,重写了父类AbstractMessageConverter的addDefaultHeaders()方法,方法的参数是String类型的数据(String s)。

    protected void addDefaultHeaders(HttpHeaders headers, String s, @Nullable MediaType type) throws IOException {

        if (headers.getContentType() == null && type != null && type.isConcrete() && (type.isCompatibleWith(MediaType.APPLICATION_JSON) || type.isCompatibleWith(APPLICATION_PLUS_JSON))) {

            headers.setContentType(type);

        }



        super.addDefaultHeaders(headers, s, type);

    }

        而在AbstractMessageConverterMethodProcessor类中执行了writeWithMessageConverters()方法负责数据格式的转化,在该方法中先执行beforeBodyWrite()方法把数据转化为Result,接着又执行了write()方法。write()方法是HttpMessageConverter类的方法,是空方法,由子类实现。在其StringHttpMessageConverter子类的实现中,内部调用addDefaultHeaders()方法,把已经是Result类型的数据作为参数传入到String类型参数的addDefaultHeaders()方法,参数类型不匹配,因此报错。

    protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType, ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

        // 该方法源码很长, 这里仅展示关键部分

        body = this.getAdvice().beforeBodyWrite(body, returnType, selectedMediaType, converter.getClass(), inputMessage, outputMessage);

        if (body != null) {

            LogFormatUtils.traceDebug(this.logger, (traceOn) -> {

                return "Writing [" + LogFormatUtils.formatValue(body, !traceOn) + "]";

            });

            this.addContentDispositionHeader(inputMessage, outputMessage);

            if (genericConverter != null) {

                genericConverter.write(body, (Type)targetType, selectedMediaType, outputMessage);

            } else {

                converter.write(body, selectedMediaType, outputMessage);

            }

        } else if (this.logger.isDebugEnabled()) {

            this.logger.debug("Nothing to write: null body");

        }

    }

下篇文章:

SpringBoot系列—统一功能处理(统一异常处理)https://blog.csdn.net/sniper_fandc/article/details/148998405?fromshare=blogdetail&sharetype=blogdetail&sharerId=148998405&sharerefer=PC&sharesource=sniper_fandc&sharefrom=from_link


网站公告

今日签到

点亮在社区的每一天
去签到