一、Controller返回值格式统一

1、WebResult类

在 Controller对外提供服务的时候,我们都需要统一返回值格式。一般定义一个 WebResult类。

统一返回值(WebResult类)格式如下:

{
  "success": true,
  "code": 200000,
  "message": null,
  "data": {
    "pageList": [
      "张三",
      "ccc"
    ],
    "paginator": {
      "currentPage": 1,
      "pageSize": 2,
      "total": 3,
      "pages": 4
    }
  }
}

WebResult类信息:

@Data
@ApiModel(value = "Web结果集")
public class WebResult<T> implements Serializable {
    private static final long serialVersionUID = -4350499690382193884L;

    /**
     * 是否成功, 默认false
     */
    @ApiModelProperty(value = "是否成功, 默认false")
    private Boolean success = false;

    /**
     * 返回状态码
     */
    @ApiModelProperty(value = "返回状态码")
    private Integer code;

    /**
     * 返回信息
     */
    @ApiModelProperty(value = "返回信息")
    private String message;

    /**
     * 返回数据
     */
    @ApiModelProperty(value = "返回数据")
    private T data;

    public static <T> WebResult<T> ok() {
        WebResult<T> webResult = new WebResult<>();
        webResult.setSuccess(true);
        webResult.setCode(WebHttpCode.SERVICE_SUCCESS);
        webResult.setMessage("操作成功");
        return webResult;
    }

    public static <T> WebResult<T> ok(T data) {
        WebResult<T> webResult = new WebResult<>();
        webResult.setSuccess(true);
        webResult.setCode(WebHttpCode.SERVICE_SUCCESS);
        webResult.setMessage("操作成功");
        webResult.setData(data);
        return webResult;
    }

    public static <T> WebResult<T> error() {
        WebResult<T> webResult = new WebResult<>();
        webResult.setSuccess(false);
        webResult.setCode(WebHttpCode.SERVICE_ERROR);
        webResult.setMessage("操作失败");
        return webResult;
    }

    public WebResult message(String message) {
        this.setMessage(message);
        return this;
    }

    public WebResult data(T data) {
        this.setData(data);
        return this;
    }

}

如果我们不做全局 Controller统一处理返回时,就出需要业务在每个 Controller类中返回 WebResult类。同时在全局异常中也返回 WebResult类。

有没有一种跟优雅的方式处理呢?当然有

  • 业务不需要对所有 Controller都使用一个返回值(WebResult类),Controller可以需要返回原始值或者自定义的基类,然后处理器统一对返回值(WebResult类)进行封装并输出。
  • 同时也可以添加自定义注解,此注解用于忽略返回值封装,按照 Controller原始值返回。

下面我们使用 HandlerMethodReturnValueHandler接口来实现。

2、相关类说明

2.1 HandlerMethodReturnValueHandler接口

使用不同策略处理从调用处理程序方法的返回值,策略处理顶层接口,自定义返回值格式需要实现此接口。

org.springframework.web.method.support.HandlerMethodReturnValueHandler

  • supportsReturnType:设置支持返回值类型
  • handleReturnValue:处理返回值基础参数

在这里插入图片描述

2.2 RequestMappingHandlerAdapter类

请求映射处理适配,包含了参数、返回值处理器等信息

org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter

  • HandlerMethodReturnValueHandlerComposite内部维护了HandlerMethodReturnValueHandler列表

在这里插入图片描述

3.3 RequestResponseBodyMethodProcessor类

属于HandlerMethodReturnValueHandler子类。

org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor

  • 主要功能是对请求和响应体的做处理的方法,所有属于RequestResponseBodyMethodProcessor的子类都需要替换为自定义返回值处理

在这里插入图片描述

全局Controller返回值统一处理实现原理就是:在bean初始化的时候,获取到所有处理器数组,然后将所有是 RequestResponseBodyMethodProcessor处理器子类对返回值处理的过程替换为自定义返回值处理器处理。
这样当调用对应返回值处理器时,将会使用到自定义的返回值处理器,也就是所有返回值都会按照规定的进行处理。
同时,我们也可以自定义注解(作用于Controller类或者方法级别忽略返回值封装),然后在自定义返回值处理器中忽略返回值封装。

二、全局Controller返回值统一处理实战

1、自定义注解

自定义注解,作用于Controller类或者方法级别忽略返回值封装。

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface IgnoreResponseBodyWrap {
}

2、创建自定义返回值处理器类

创建自定义处理器类实现 HandlerMethodReturnValueHandler接口,主要用于实现自定义返回值内容,不需要注入容器。

下面代码中的 BaseXXXResult是业务的基类。你可以自定义或者使用你们约定的基类。

/**
 * Controller 返回值格式统一处理
 */
public class ResponseBodyWrapHandler implements HandlerMethodReturnValueHandler {

    private final HandlerMethodReturnValueHandler delegate;

    public ResponseBodyWrapHandler(HandlerMethodReturnValueHandler delegate) {
        this.delegate = delegate;
    }

    /**
     * 设置支持返回值类型
     *
     * @param returnType
     * @return
     */
    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
        return delegate.supportsReturnType(returnType);
    }

    /**
     * 处理返回值基础参数
     *
     * @param returnValue  方法的返回值对象
     * @param returnType   封装方法参数说明的辅助类(方法的返回值类型)
     * @param mavContainer 用于设置模型和视图的容器
     * @param webRequest   当前的请求对象
     * @throws Exception
     */

    @Override
    public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
        // 如果类或者方法含有不包装注解则忽略包装
        IgnoreResponseBodyWrap ignoreResponseBodyWrap = returnType.getDeclaringClass().getAnnotation(IgnoreResponseBodyWrap.class);
        if (ignoreResponseBodyWrap != null) {
            delegate.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
            return;
        }
        ignoreResponseBodyWrap = returnType.getMethodAnnotation(IgnoreResponseBodyWrap.class);
        if (ignoreResponseBodyWrap != null) {
            delegate.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
            return;
        }

        // 返回统一格式
        Object obj = null;
        if (returnValue instanceof WebResult) {
            obj = returnValue;
        } else if (returnValue instanceof BasePageResult) {
            BasePageResult basePageResult = (BasePageResult) returnValue;
            ErrorCode errorCode = basePageResult.getErrorCode();
            Map<String, Object> pageData = new HashMap<>();
            pageData.put(CommonConstants.PAGE_LIST, basePageResult.getPageList());
            pageData.put(CommonConstants.PAGINATOR, basePageResult.getPaginator());
            obj = WebResult.ok().data(pageData).message(errorCode == null ? null : errorCode.getMessage());
        } else if (returnValue instanceof BaseOperateResult) {
            BaseOperateResult baseOperateResult = (BaseOperateResult) returnValue;
            ErrorCode errorCode = baseOperateResult.getErrorCode();
            boolean success = baseOperateResult.isSuccess();
            if (success) {
                obj = WebResult.ok().data(baseOperateResult.getId()).message(errorCode == null ? null : errorCode.getMessage());
            } else {
                obj = WebResult.error().message(errorCode == null ? null : errorCode.getMessage());
            }
        } else if (returnValue instanceof BaseDataResult) {
            BaseDataResult baseDataResult = (BaseDataResult) returnValue;
            ErrorCode errorCode = baseDataResult.getErrorCode();
            boolean success = baseDataResult.isSuccess();
            if (success) {
                obj = WebResult.ok().data(baseDataResult.getData()).message(errorCode == null ? null : errorCode.getMessage());
            } else {
                obj = WebResult.error().message(errorCode == null ? null : errorCode.getMessage());
            }
        } else if (returnValue instanceof BaseResult) {
            BaseResult baseResult = (BaseResult) returnValue;
            ErrorCode errorCode = baseResult.getErrorCode();
            boolean success = baseResult.isSuccess();
            if (success) {
                obj = WebResult.ok().message(errorCode == null ? null : errorCode.getMessage());
            } else {
                obj = WebResult.error().message(errorCode == null ? null : errorCode.getMessage());
            }
        } else {
            obj = WebResult.ok(returnValue);
        }

        delegate.handleReturnValue(obj, returnType, mavContainer, webRequest);
    }
}

3、注册自定义返回值处理器类

将所有 RequestResponseBodyMethodProcessor返回值处理器替换为自定义的返回值处理器。

@Component
public class ResponseBodyWrapFactoryBean implements InitializingBean {
    private final RequestMappingHandlerAdapter adapter;

    @Autowired
    public ResponseBodyWrapFactoryBean(RequestMappingHandlerAdapter adapter) {
        this.adapter = adapter;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        List<HandlerMethodReturnValueHandler> returnValueHandlers = adapter.getReturnValueHandlers();
        if (returnValueHandlers.size() > 0) {
            // 将内置的返回值处理器进行替换
            List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>(returnValueHandlers);
            decorateHandlers(handlers);
            adapter.setReturnValueHandlers(handlers);
        }
    }

    /**
     * 将所有RequestResponseBodyMethodProcessor返回值处理器替换为自定义的返回值处理器
     *
     * @author tianxincode@163.com
     * @since 2020/10/12
     */
    private void decorateHandlers(List<HandlerMethodReturnValueHandler> handlers) {
        for (HandlerMethodReturnValueHandler handler : handlers) {
            if (handler instanceof RequestResponseBodyMethodProcessor) {
                // 替换为自定义返回值处理器
                ResponseBodyWrapHandler decorator = new ResponseBodyWrapHandler(handler);
                int index = handlers.indexOf(handler);
                handlers.set(index, decorator);
                break;
            }
        }
    }

}

4、全局异常处理类

一般项目都会有一个全局异常处理类

@Slf4j
@ResponseBody
@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(value = Exception.class)
    private WebResult handlerException(Exception e) {
        log.error("Exception 异常 -> error:", e);
        WebResult webResult = new WebResult();
        webResult.setSuccess(false);
        webResult.setCode(WebHttpCode.SYSTEM_ERROR);
        webResult.setMessage("系统异常,请联系管理员");
        return webResult;
    }

    @ExceptionHandler(value = RuntimeException.class)
    private WebResult handlerRuntimeException(RuntimeException e) {
        log.error("RuntimeException 异常 -> error:", e);
        WebResult webResult = new WebResult();
        webResult.setSuccess(false);
        webResult.setCode(WebHttpCode.SYSTEM_ERROR);
        webResult.setMessage("系统异常");
        return webResult;
    }


    @ExceptionHandler(value = ServiceException.class)
    private WebResult handlerServiceException(ServiceException e) {
        log.error("ServiceException 异常 -> error:", e);
        WebResult webResult = new WebResult();
        webResult.setSuccess(false);
        webResult.setCode(WebHttpCode.SERVICE_ERROR);
        webResult.setMessage(e.getMessage());
        return webResult;
    }

}

5、Controller示例

@RestController
@RequestMapping("/project2")
@Slf4j
@Api(value = "Project2Controller", tags = {"Project2相关操作接口"})
public class Project2Controller {


    @GetMapping("/successPage")
    @ApiOperation(value = "successPage接口")
    public BaseResult successPage() {
        log.info("successPage接口");

        List list = new ArrayList();
        list.add("张三");
        list.add("ccc");

        BasePageResult basePageResult = new BasePageResult<>();
        basePageResult.setSuccess(Boolean.TRUE);

        basePageResult.setPageList(list);
        basePageResult.setPaginator(new Paginator(1, 2, 3, 4));
        return basePageResult;
    }

    @GetMapping("/get")
    @ApiOperation(value = "get接口")
    @IgnoreResponseBodyWrap
    public List get() {
        log.info("get接口");

        ProjectDO projectDO = new ProjectDO();
        projectDO.setId(10L);
        projectDO.setName("项目10");
        projectDO.setDepartmentId(0L);
        projectDO.setDescr("");
        projectDO.setDelFlag(false);
        projectDO.setCreateTime(new Date());
        projectDO.setUpdateTime(new Date());


        List list = new ArrayList();
        list.add("张三");
        list.add("ccc");
        return list;
    }

    @GetMapping("/get2")
    @ApiOperation(value = "get2接口")
    public BaseDataResult get2() {
        log.info("get2接口");

        ProjectDO projectDO = new ProjectDO();
        projectDO.setId(10L);
        projectDO.setName("项目10");
        projectDO.setDepartmentId(0L);
        projectDO.setDescr("");
        projectDO.setDelFlag(false);
        projectDO.setCreateTime(new Date());
        projectDO.setUpdateTime(new Date());

        BaseDataResult baseDataResult = new BaseDataResult();
        baseDataResult.setSuccess(Boolean.TRUE);
        baseDataResult.setData(projectDO);

        return baseDataResult;
    }

    @GetMapping("/get3")
    @ApiOperation(value = "get3接口")
    public List get3() {
        log.info("get3接口");

        ProjectDO projectDO = new ProjectDO();
        projectDO.setId(10L);
        projectDO.setName("项目10");
        projectDO.setDepartmentId(0L);
        projectDO.setDescr("");
        projectDO.setDelFlag(false);
        projectDO.setCreateTime(new Date());
        projectDO.setUpdateTime(new Date());


        List list = new ArrayList();
        list.add("张三");
        list.add("ccc");
        return list;
    }

    /**
     * 文件下载接口
     */
    @GetMapping("/download")
    @ApiOperation(value = "文件下载接口")
    public void download(HttpServletResponse response) throws SerialException {

        try {
            // 获取要下载的文件
            File file = new File("D:\\TempFiles\\xxxxxx.xlsx");

            String fileName = file.getName();
            response.setContentType("application/octet-stream;charset=UTF-8");
            response.setHeader("Content-Disposition", "attachment;filename=" + new String(fileName.getBytes("UTF-8"), "iso-8859-1"));

            IOUtils.copy(new FileInputStream(file), response.getOutputStream());
        } catch (Exception e) {
            log.info("文件下载异常 -> e=", e);
            throw new ServiceException("文件下载异常");
        }

    }



    @GetMapping("/systemError")
    @ApiOperation(value = "systemError接口")
    public BaseResult systemError() {
        log.info("systemError接口");

        int i = 2 / 0;

        BaseResult baseResult = new BaseResult();
        baseResult.setSuccess(Boolean.TRUE);
        return baseResult;
    }

}

在这里插入图片描述

– 求知若饥,虚心若愚。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐