前几天搬砖的时候,发现所有接口方法都定义了一样的返回值,不能真正地将业务逻辑表达出来,没有达到“望文生意”的效果。着手改造一下。
虽然标题是Spring Boot,但是这个接口在包 spring-webmvc.jar
下(请原谅我这个标题党)。 ResponseBodyAdvice
接口类路径:
org.springframework.web.servlet.mvc.method.annotation
首先,我们需要定义一个统一的状态码 ResultCode
,来统一工程中异常情况的描述:
/** * 统一状态返回码 * 三级状态码不满足时,可返回二级宏观码,依次类推 * 状态码参考 alibaba 《JAVA开发手册-泰山版》 */ public enum ResultCode { OK("00000", "成功"), /** 一级宏观错误码 */ CLIENT_ERROR("A0001", "用户端错误 "), /** 二级宏观错误码 */ USER_REGISTER_ERROR("A0100", "用户注册错误"), USER_DISAGREE_PRIVACY_PROTOCOL("A0101", "用户未同意隐私协议"), REGION_REGISTER_LIMITED("A0102","注册国家或地区受限"), VALIDATE_USERNAME_FAILED("A0110","用户名校验失败"), USERNAME_EXISTED("A0111","用户名已存在"), /* 中间还有好多,鉴于篇幅,就不贴出来了 */ MAIL_NOTICE_FAILED("C0503", "邮件提醒服务失败"); private String status; private String message; ResultCode(String status, String message) { this.status = status; this.message = message; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } @Override public String toString() { return "ResultCode{" + "status='" + status + '/'' + ", message='" + message + '/'' + '}'; } }
枚举的好处我就不多说了,想必大家也经常使用 HttpStatus
这个枚举。
其次,我们封装一个数据传输对象 Result
,这个类就用来封装我们统一的返回格式:
import com.jason.enums.ResultCode; import com.fasterxml.jackson.annotation.JsonView; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import org.springframework.data.domain.Page; import java.util.Collection; import java.util.HashMap; import java.util.Map; /** * 统一返回DTO * 通过 JsonView 尽可能控制返回格式的精简,酌情使用 */ @ApiModel public class Result { public interface commonResult{}; public interface standardResult extends commonResult{}; @ApiModelProperty(value = "status", name = "响应状态码") private String status; @ApiModelProperty(value = "message", name = "响应信息") private String message; @ApiModelProperty(value = "body", name = "响应内容") private Object body; public Result() { this.status = ResultCode.OK.getStatus(); this.message = ResultCode.OK.getMessage(); } public Result(ResultCode resultCode) { this.status = resultCode.getStatus(); this.message = resultCode.getMessage(); } public Result(ResultCode resultCode, String message) { this(resultCode); this.message = message; } public Result(Object body) { this.status = ResultCode.OK.getStatus(); this.message = ResultCode.OK.getMessage(); this.body = body; } public Result(ResultCode resultCode, Object body) { this(body); this.status = resultCode.getStatus(); this.message = resultCode.getMessage(); } public Result(Collection collection) { this.status = ResultCode.OK.getStatus(); this.message = ResultCode.OK.getMessage(); this.body = collection; } public Result(ResultCode resultCode, Collection collection) { this(collection); this.status = resultCode.getStatus(); this.message = resultCode.getMessage(); } public Result(Page page) { this.status = ResultCode.OK.getStatus(); this.message = ResultCode.OK.getMessage(); Map<String, Object> info = new HashMap<>(8); info.put("totalItem", page.getTotalElements()); info.put("pageSize", page.getNumber()); info.put("pageNum", page.getSize()); info.put("totalPage", page.getTotalPages()); info.put("item", page.getTotalElements()); this.body = info; } public Result(ResultCode resultCode, Page page) { this(page); this.status = resultCode.getStatus(); this.message = resultCode.getMessage(); } @JsonView(commonResult.class) public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } @JsonView(commonResult.class) public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } @JsonView(standardResult.class) public Object getBody() { return body; } public void setBody(Object body) { this.body = body; } @Override public String toString() { return "Result{" + "status='" + status + '/'' + ", message='" + message + '/'' + ", body=" + body + '}'; } }
这个类每个人定义的方式不一样,包括构造方法或者是否用 static
修饰,大家都可以根据自身情况实现。
最后,我们再实现接口 ResponseBody
,可以根据一些条件来控制:
import com.jason.dto.Result; import com.jason.enums.ResultCode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.MethodParameter; 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.ControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; /** * 统一返回对象配置类 * create by Jason */ @ControllerAdvice public class ResultBodyConfig implements ResponseBodyAdvice<Object> { private Logger logger = LoggerFactory.getLogger(this.getClass()); private final static String PACKAGE_PATH = "com.jason.component"; /** * 针对以下情况 不做 统一包装处理 * 1.返回值为 void 的方法 * 2.返回值为 String 类型的方法 * 3.返回值为 Result 类型的方法 * 4.在包路径 PACKAGE_PATH 以外的方法 * @param methodParameter * @param aClass * @return */ @Override public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) { return !methodParameter.getMethod().getReturnType().isAssignableFrom(Void.TYPE) && !methodParameter.getMethod().getReturnType().isAssignableFrom(String.class) && !methodParameter.getMethod().getReturnType().isAssignableFrom(Result.class) && methodParameter.getDeclaringClass().getPackage().getName().contains(PACKAGE_PATH); } @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { if (body instanceof Result) { return body; } return new Result(ResultCode.OK, body); } }
方法 supports
返回 true
则会执行 beforeBodyWrite
方法,否则会跳过,继续按照原来接口方法的返回值进行返回。
这里谈一下我过滤这些条件的想法:
SpringMVC
中我们会返回字符串来匹配模板,虽然现在都是前后端分离的项目,但是还是按照约定俗成或者是第一反应,将 String
排除。 Result
的方法:我们已经做了封装,不需要在封装一次了。 Swagger
。 经过以上这种处理,我们在写接口的时候就可以放心大胆的定义业务需要的返回值了,真正的实现了接口方法就可以描述业务的初衷。
除了这种方式,大家还可以通过过滤器来实现这个功能,这里就不做说明了。