美文网首页it
SpringBoot统一API魔改

SpringBoot统一API魔改

作者: Jingzhiwindy | 来源:发表于2022-06-11 15:09 被阅读0次

    配置注解

    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
    }
    

    相关文章

      网友评论

        本文标题:SpringBoot统一API魔改

        本文链接:https://www.haomeiwen.com/subject/kbbgmrtx.html