配置注解
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;
@Component
public 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;
@RestControllerAdvice
public 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
@Setter
public 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;
@RestController
public 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
}
网友评论