设置注解
package com.example.demo.api;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target({ElementType.TYPE, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)public @interface API { boolean request() default true; boolean response() default true;}以注解的情势对接口举行设置,可设置在Controller的类或方法上。request属性为true,表现哀求体需要分公有域和私有域,私有域数据封装在data节点内;为false,表现不区分公有域和私有域。response同理,如果属性为true,会将返回的业务数据封装在data节点内。
相应码的界说
package com.example.demo.api;import cn.hutool.core.util.StrUtil;import com.fasterxml.jackson.annotation.JsonCreator;import com.fasterxml.jackson.annotation.JsonValue;import java.util.Arrays;public enum State { SUCCESS(0, "success"), VALID_FAIL(100, "valid fail"), PASSWORD_ERROR(101, "password error"), UNKNOWN_ERROR(999, "unknow error"); @JsonValue private int code; private String message; State(int code, String message) { this.code = code; this.message = message; } public int getCode() { return code; } public String getMessage() { return message; } @Override public String toString() { return String.format("[%03d] %s", code, message); } public void tryThrow() { throw new StatefulException(this); } public void tryThrow(String message) { throw new StatefulException(this, message); } public void tryThrow(String messageTemplate, Object... params) { throw new StatefulException(this, messageTemplate, params); } public void tryThrow(Throwable cause) { throw new StatefulException(this, cause); } public void isTrue(boolean expression) { isFalse(!expression); } public void isTrue(boolean expression, String message) { isFalse(!expression, message); } public void isTrue(boolean expression, String messageTemplate, Object... params) { isFalse(!expression, messageTemplate, params); } public void isFalse(boolean expression) { if (expression) { throw new StatefulException(this); } } public void isFalse(boolean expression, String message) { if (expression) { throw new StatefulException(this, message); } } public void isFalse(boolean expression, String messageTemplate, Object... params) { if (expression) { throw new StatefulException(this, messageTemplate, params); } } public <T> T notNull(T obj) { if (obj == null) { throw new StatefulException(this); } return obj; } public <T> T notNull(T obj, String message) { if (obj == null) { throw new StatefulException(this, message); } return obj; } public <T> T notNull(T obj, String messageTemplate, Object... params) { if (obj == null) { throw new StatefulException(this, messageTemplate, params); } return obj; } public <T extends CharSequence> T notBlank(CharSequence str) { if (StrUtil.isBlank(str)) { throw new StatefulException(this); } return (T) str; } public <T extends CharSequence> T notBlank(CharSequence str, String message) { if (StrUtil.isBlank(str)) { throw new StatefulException(this, message); } return (T) str; } public <T extends CharSequence> T notBlank(CharSequence str, String messageTemplate, Object... params) { if (StrUtil.isBlank(str)) { throw new StatefulException(this, messageTemplate, params); } return (T) str; } public <T> List<T> notEmpty(List<T> list) { if (list == null || list.isEmpty()) { tryThrow(); } return list; } public <T> List<T> notEmpty(List<T> list, String errorMessage) { if (list == null || list.isEmpty()) { tryThrow(errorMessage); } return list; } public <T> List<T> notEmpty(List<T> list, String messageTemplate, Object... params) { if (list == null || list.isEmpty()) { tryThrow(messageTemplate, params); } return list; } @JsonCreator public static State valueOf(int code) { return Arrays.stream(values()).filter(state -> state.getCode() == code).findAny().orElse(null); }}界说了错误码、错误信息,以及一些断言。使用断言可以抛出包罗错误码和错误信息的非常
支持相应码的非常
package com.example.demo.api;import cn.hutool.core.util.StrUtil;public class StatefulException extends RuntimeException { private State state; public StatefulException(State state) { this.state = state; } public StatefulException(State state, String message) { super(message); this.state = state; } public StatefulException(State state, String messageTemplate, Object... params) { super(StrUtil.format(messageTemplate, params)); this.state = state; } public StatefulException(State state, Throwable cause) { super(cause); this.state = state; } public State getState() { return state; } public void setState(State state) { this.state = state; } @Override public String toString() { return StrUtil.isBlank(getMessage()) ? state.toString() : state + ": " + getMessage(); }}公有域的界说
package com.example.demo.api;import java.io.Serializable;public class PublicDomain<T> implements Serializable { private T data; public PublicDomain() { } public PublicDomain(T data) { this.data = data; } public T getData() { return data; } public void setData(T data) { this.data = data; }}将哀求和相应分为公有域部门和私有域部门。相应中公有域包罗相应码、相应信息和由data封装的私有域。
相应体的界说
package com.example.demo.api;import com.fasterxml.jackson.annotation.JsonInclude;import com.fasterxml.jackson.annotation.JsonPropertyOrder;@JsonPropertyOrder({"resCode", "resMessage", "errorMessage", "data"})public class ResultVo<T> extends PublicDomain<T> { // 相应状态码 private int resCode; // 相应状态信息 private String resMessage; // 错误详情 @JsonInclude(JsonInclude.Include.NON_NULL) private String errorMessage; public ResultVo() { this(State.SUCCESS); } public ResultVo(T data) { super(data); this.resCode = State.SUCCESS.getCode(); this.resMessage = State.SUCCESS.getMessage(); } public ResultVo(State state) { this.resCode = state.getCode(); this.resMessage = state.getMessage(); } public ResultVo(StatefulException statefulException) { State state = statefulException.getState(); this.resCode = state.getCode(); this.resMessage = state.getMessage(); this.errorMessage = statefulException.getMessage(); } public ResultVo(State state, Throwable ex) { this.resCode = state.getCode(); this.resMessage = state.getMessage(); this.errorMessage = ex.getMessage(); } public int getResCode() { return resCode; } public void setResCode(int resCode) { this.resCode = resCode; } public String getResMessage() { return resMessage; } public void setResMessage(String resMessage) { this.resMessage = resMessage; } public String getErrorMessage() { return errorMessage; } public void setErrorMessage(String errorMessage) { this.errorMessage = errorMessage; }}哀求体的处置惩罚
package com.example.demo.api;import com.fasterxml.jackson.databind.JsonNode;import com.fasterxml.jackson.databind.ObjectMapper;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.core.MethodParameter;import org.springframework.http.HttpHeaders;import org.springframework.http.HttpInputMessage;import org.springframework.http.converter.HttpMessageConverter;import org.springframework.util.StreamUtils;import org.springframework.web.bind.annotation.RestControllerAdvice;import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter;import java.io.ByteArrayInputStream;import java.io.IOException;import java.io.InputStream;import java.lang.reflect.Type;import java.nio.charset.StandardCharsets;@RestControllerAdvice(basePackages = "com.example.demo")public class RequesstApiAdvice extends RequestBodyAdviceAdapter { @Autowired private ObjectMapper objectMapper; @Override public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) { API apiAnn = methodParameter.hasMethodAnnotation(API.class) ? methodParameter.getMethodAnnotation(API.class) : methodParameter.getDeclaringClass().getAnnotation(API.class); return apiAnn != null && apiAnn.request() && !PublicDomain.class.equals(methodParameter.getParameterType()); } @Override public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException { JsonNode jsonNode = objectMapper.readTree(inputMessage.getBody()); return new HttpInputMessage() { @Override public InputStream getBody() { JsonNode dataJsonNode = State.VALID_FAIL.notNull(jsonNode.get("data"), "哀求体未包罗私有域节点data"); return new ByteArrayInputStream(dataJsonNode.toString().getBytes(StandardCharsets.UTF_8)); } @Override public HttpHeaders getHeaders() { return inputMessage.getHeaders(); } }; }}相应体的处置惩罚
相应体的处置惩罚可以基于ResponseBodyAdvice,也可以基于HandlerMethodReturnValueHandler,两种方案可以任选其一
基于ResponseBodyAdvice的实现
package com.example.demo.api;import org.springframework.core.MethodParameter;import org.springframework.http.MediaType;import org.springframework.http.server.ServerHttpRequest;import org.springframework.http.server.ServerHttpResponse;import org.springframework.web.bind.annotation.RestControllerAdvice;import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;@RestControllerAdvice(basePackages = "com.example.demo")public class ResponseApiAdvice implements ResponseBodyAdvice { @Override public boolean supports(MethodParameter returnType, Class converterType) { API apiAnn = returnType.hasMethodAnnotation(API.class) ? returnType.getMethodAnnotation(API.class) : returnType.getDeclaringClass().getAnnotation(API.class); return apiAnn != null && apiAnn.response(); } @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { return body instanceof ResultVo ? body : new ResultVo<>(body); }}基于HandlerMethodReturnValueHandler的实现
package com.example.demo.api;import org.springframework.core.MethodParameter;import org.springframework.stereotype.Component;import org.springframework.web.context.request.NativeWebRequest;import org.springframework.web.method.support.HandlerMethodReturnValueHandler;import org.springframework.web.method.support.ModelAndViewContainer;import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor;import java.util.ArrayList;import java.util.List;@Componentpublic class ApiHandlerMethodReturnValueHandler implements HandlerMethodReturnValueHandler { private HandlerMethodReturnValueHandler handler; public ApiHandlerMethodReturnValueHandler(RequestMappingHandlerAdapter requestMappingHandlerAdapter) { List<HandlerMethodReturnValueHandler> originHandlers = requestMappingHandlerAdapter.getReturnValueHandlers(); List<HandlerMethodReturnValueHandler> newHandlers = new ArrayList<>(originHandlers.size()); for (HandlerMethodReturnValueHandler originHandler : originHandlers) { if (originHandler instanceof RequestResponseBodyMethodProcessor) { newHandlers.add(this); handler = originHandler; } else { newHandlers.add(originHandler); } } requestMappingHandlerAdapter.setReturnValueHandlers(newHandlers); } @Override public boolean supportsReturnType(MethodParameter returnType) { API apiAnn = returnType.hasMethodAnnotation(API.class) ? returnType.getMethodAnnotation(API.class) : returnType.getDeclaringClass().getAnnotation(API.class); return handler.supportsReturnType(returnType) && apiAnn != null && apiAnn.response(); } @Override public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { Object response = returnValue instanceof ResultVo ? returnValue : new ResultVo<>(returnValue); handler.handleReturnValue(response, returnType, mavContainer, webRequest); }}HandlerMethodReturnValueHandler 的作用是对处置惩罚器的处置惩罚效果再举行一次二次加工
全局非常处置惩罚
package com.example.demo.api;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.bind.annotation.RestControllerAdvice;@RestControllerAdvicepublic class GlobalExceptionResolver { private final static Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionResolver.class); @ExceptionHandler(Exception.class) public ResultVo exceptionHandle(Exception ex) { LOGGER.error("error", ex); return new ResultVo(State.UNKNOWN_ERROR, ex); } @ExceptionHandler(RuntimeException.class) public ResultVo exceptionHandle(RuntimeException ex) { LOGGER.error("valid fail", ex); return new ResultVo(State.VALID_FAIL, ex); } @ExceptionHandler(StatefulException.class) public ResultVo exceptionHandle(StatefulException ex) { LOGGER.error(ex.getState().toString(), ex); return new ResultVo(ex); }}测试
package com.example.demo.user.entity;import lombok.Getter;import lombok.Setter;@Getter@Setterpublic class User { private String account; private String name; private String password;}package com.example.demo.user.controller;import com.example.demo.api.API;import com.example.demo.api.State;import com.example.demo.user.entity.User;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RestController;@RestControllerpublic class UserController { @API @PostMapping("/login/passport") public User test(@RequestBody User user) { State.VALID_FAIL.notBlank(user.getAccount(), "账号为空"); State.VALID_FAIL.notBlank(user.getPassword(), "暗码为空"); State.PASSWORD_ERROR.isTrue(user.getPassword().equals("123456"), "暗码错误"); return user; }}精确哀求
{ "data": { "account": "demo", "password": "123456" }}相应
{ "resCode": 0, "resMessage": "success", "data": { "account": "demo", "name": null, "password": "123456" }}暗码错误哀求
{ "data": { "account": "demo", "password": "123" }}相应
{ "resCode": 101, "resMessage": "password error", "errorMessage": "暗码错误", "data": null} |