问题
- 前端一直不太认同http status(状态码)表达操作成功与否的方式,强烈要求所有接口都返回200,在响应体中增加code来表示操作是否成功。
比如当使用POST方式请求接口/api/user/2 时
当没有操作权限时两种响应
请求接口: /api/user/2
请求方法: POST
请求体:......(json格式的参数)
原响应格式: http status: 403 响应体:
{
msg: '没有操作权限'
}
前端想要的响应格式: http status: 200 响应体:
{
code: 403,
msg: '没有操作权限'
}
办法
1. 修改每个接口的返回值
最简单粗暴的方式:定义一个类如下:
@Data
public class Res{
private Integer code;
private String msg;
private Object content;
}
将每个service的返回值都set给content,set相应的code和msg,然后controller将该对象返回给前端。
也可以每个接口都返回JSONObject的实例json
@GetMapping("/{id}")
public JSONObject getOne((@PathVariable Integer id){
Person person = personService.findById(id);
JSONObject json = new JSONObject();
json.put("code", 200);
json.put("msg", "成功");
json.put("content", person);
return json;
}
功能完成,但是看到满屏的Res或者JSONObject操作,这么多重复代码,实在不应该。
2. 改进,修改SpringBoot原有流程
SpringBoot的请求处理过程如下:
请求处理大致流程 请求处理具体流程
在第二张图中可以看到HandlerMethodReturnValueHandler在处理所有响应的内容,只要干预他的处理过程就能将原有数据包装起来 下面是该类的源码
public interface HandlerMethodReturnValueHandler {
/**
* Whether the given {@linkplain MethodParameter method return type} is
* supported by this handler.
* @param returnType the method return type to check
* @return {@code true} if this handler supports the supplied return type;
* {@code false} otherwise
*/
boolean supportsReturnType(MethodParameter returnType);
/**
* Handle the given return value by adding attributes to the model and
* setting a view or setting the
* {@link ModelAndViewContainer#setRequestHandled} flag to {@code true}
* to indicate the response has been handled directly.
* @param returnValue the value returned from the handler method
* @param returnType the type of the return value. This type must have
* previously been passed to {@link #supportsReturnType} which must
* have returned {@code true}.
* @param mavContainer the ModelAndViewContainer for the current request
* @param webRequest the current request
* @throws Exception if the return value handling results in an error
*/
void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;
}
实现该类的两个方法就能自定义处理过程。
- 首先还是要定义一个工具类:
public class Res {
// protected final static Res OK = new Res( HttpStatus.OK.value(),"成功","");
private Integer code;
private String msg;
private String message;
/**
* 返回正常信息时,将数据放入该字段
*/
private Object content;
/**
* 成功响应并需要返回数据时,使用该构造方法
* @param data
*/
protected Res(Object data) {
this.code = 200;
this.msg = "成功";
this.message = "success";
this.content = data;
}
Res(Integer code,String msg, String message){
this.msg = msg;
this.code = code;
this.message = message;
}
public Integer getCode() {
return code;
}
public String getMsg() {
return msg;
}
public String getMessage() {
return message;
}
public Object getContent() {
return content;
}
}
- 然后实现 HandlerMethodReturnValueHandler
public class WrapReturnValueHandler implements HandlerMethodReturnValueHandler {
private Res ok = new Res(null);
private RequestResponseBodyMethodProcessor target;
public WrapReturnValueHandler(RequestResponseBodyMethodProcessor target) {
this.target = target;
}
@Override
public boolean supportsReturnType(MethodParameter methodParameter) {
return true;
}
@Override
public void handleReturnValue(Object o, MethodParameter methodParameter,
ModelAndViewContainer modelAndViewContainer,
NativeWebRequest nativeWebRequest) throws Exception {
Executable executable = methodParameter.getExecutable();
Method method = (Method) executable;
Class<?> returnType = method.getReturnType();
if (returnType.equals(Void.class)) {
target.handleReturnValue(ok, methodParameter, modelAndViewContainer, nativeWebRequest);
} else if ( returnType.equals( Res.class )) {
target.handleReturnValue(o, methodParameter, modelAndViewContainer, nativeWebRequest);
else {
target.handleReturnValue(new Res(o), methodParameter, modelAndViewContainer, nativeWebRequest);
}
}
}
- 最后将这个实现类加入到SpringBoot流程中
@Configuration
public class InitializingAdvice implements InitializingBean {
private final static Logger log = LoggerFactory.getLogger( InitializingAdvice.class );
@Resource
private RequestMappingHandlerAdapter adapter;
@Override
public void afterPropertiesSet() {
List<HandlerMethodReturnValueHandler> returnValueHandlers = adapter.getReturnValueHandlers();
List<HandlerMethodReturnValueHandler> handlers = new ArrayList(returnValueHandlers);
this.decorateHandlers(handlers);
adapter.setReturnValueHandlers(handlers);
}
private void decorateHandlers(List<HandlerMethodReturnValueHandler> handlers) {
for (HandlerMethodReturnValueHandler handler : handlers) {
if (handler instanceof RequestResponseBodyMethodProcessor) {
WrapReturnValueHandler decorator = new WrapReturnValueHandler(
(RequestResponseBodyMethodProcessor) handler);
int index = handlers.indexOf(handler);
handlers.set(index, decorator);
break;
}
}
}
}
这样所有的响应在转化为流之前就能将内容包装成预期格式 当然抛出异常可以使用注解@ControllerAdvice捕获控制,以控制http status,然后主动将响应构造成Res实例