Spring Cloud的全局封裝實踐

前言

跨應用的全局封裝經過模仿java的異常拋出實現,經過各個服務使用相同的接口返回格式實現服務消息的傳遞,在規範化的同時快速定位真正出現問題的服務。java

全局接口返回格式分爲外部接口格式和內部接口格式,這裏簡化爲內外接口格式一致。spring

總體流程以下:json

clipboard.png

全局返回封裝類

整個spring cloud服務的統一接口app

public class ResponseResult<T> implements Serializable {

    /**
     *
     */
    private static final long serialVersionUID = 1679552421651455773L;

    private int status; //狀態碼,根據服務實際需求自定義

    private String msg;

    private T data;

    private String url; //請求的url

    private Long host; //出現問題的根服務

    public static ResponseResult ok(Object data, String url) {
        return new ResponseResult(ResponseStatusCode.OK, data, url, null);
    }

    public static ResponseResult ok(String msg, Object data, String url) {
        return new ResponseResult(ResponseStatusCode.OK, msg, data, url, null);
    }

    public static ResponseResult ok(String msg, String url) {
        return new ResponseResult(ResponseStatusCode.OK, msg, null, url, null);
    }

    public static ResponseResult fail(int status, String msg, String url, Long host) {
        return new ResponseResult(status, msg, url, host);
    }

    public ResponseResult() {
    }

    public ResponseResult(String msg, T data, String url, Long host) {
        this.msg = msg;
        this.data = data;
        this.url = url;
        this.host = host;
    }

    public ResponseResult(int status, String msg, String url, Long host) {
        this.status = status;
        this.msg = msg;
        this.url = url;
        this.host = host;
    }

    public ResponseResult(int status, T data, String url, Long host) {
        this.status = status;
        this.data = data;
        this.url = url;
        this.host = host;
    }

    public ResponseResult(int status, String msg, T data, String url, Long host) {
        this.status = status;
        this.msg = msg;
        this.data = data;
        this.url = url;
        this.host = host;
    }

    public ResponseResult(int status, String msg, T data, Long host) {
        this.status = status;
        this.msg = msg;
        this.data = data;
        this.host = host;
    }

    public int getStatus() {
        return status;
    }

    public void setStatus(int status) {
        this.status = status;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public Long getHost() {
        return host;
    }

    public void setHost(Long host) {
        this.host = host;
    }
}

狀態碼常量類

這個類爲總體服務定義的狀態碼,能夠使用枚舉實現ide

public class ResponseStatusCode {

    /**
     * OK
     */
    public static final int OK = 0;

    /**
     * 未知異常
     */
    public static final int UNKNOW_EXCEPTION = 100;

    /**
     * 參數異常
     */
    public static final int ARGUMENT_EXCEPTION = 104;
    
    /**
     * 自定義異常
     */
    public static final int ARGUMENT_EXCEPTION = XXX;
}

自定義異常類

自定義異常類繼承RuntimeExceptionthis

public class CustomException extends RuntimeException{
    public static final long serialVersionUID = 1L;
    private int status;
    private Long host;

    public CustomException(int status) {
        this.status = status;
    }

    /**
     * 拋出異常使用自定義的異常碼
     * @param status 自定義異常碼
     * @param message 異常信息
     */
    public CustomException(int status, String message) {
        super(message);
        this.status = status;
    }

    public CustomException(String message, Throwable cause, int status) {
        super(message, cause);
        this.status = status;
    }

    public CustomException(Throwable cause, int status) {
        super(cause);
        this.status = status;
    }

    public CustomException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, int status) {
        super(message, cause, enableSuppression, writableStackTrace);
        this.status = status;
    }

    public CustomException(int status, Long host) {
        this.status = status;
        this.host = host;
    }

    /**
     * 拋出異常使用自定義的異常碼
     * @param status 自定義異常碼
     * @param message 異常信息
     * @param host 主機
     */
    public CustomException(int status, String message, Long host) {
        super(message);
        this.status = status;
        this.host = host;
    }

    public CustomException(String message, Throwable cause, int status, Long host) {
        super(message, cause);
        this.status = status;
        this.host = host;
    }

    public CustomException(Throwable cause, int status, Long host) {
        super(cause);
        this.status = status;
        this.host = host;
    }

    public CustomException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, int status, Long host) {
        super(message, cause, enableSuppression, writableStackTrace);
        this.status = status;
        this.host = host;
    }

    public int getStatus() {
        return status;
    }

    public void setStatus(int status) {
        this.status = status;
    }

    public Long getHost() {
        return host;
    }

    public void setHost(Long host) {
        this.host = host;
    }
}

接口全局響應處理類

使用spring自帶controller響應處理類,使用全局封裝類封裝返回url

@ControllerAdvice
public class CustomResponseAdivce implements ResponseBodyAdvice<Object> {

    // 這個方法表示對於哪些請求要執行beforeBodyWrite,返回true執行,返回false不執行
    @Override
    public boolean supports(MethodParameter methodParameter, Class aClass) {
        return true;
    }

    // 對於返回的對象若是不是最終對象ResponseResult,則選包裝一下
    @Override
    public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class aClass,
                                  ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        ServletServerHttpRequest servletServerHttpRequest = (ServletServerHttpRequest) serverHttpRequest;
        String url = servletServerHttpRequest.getServletRequest().getRequestURL().toString();

        if (!(o instanceof ResponseResult)) {
            ResponseResult responseResult = null;
            // 由於handler處理類的返回類型是String,爲了保證一致性,這裏須要將ResponseResult轉回去
            if (o instanceof String) {
                responseResult = ResponseResult.ok(o, url);
                ObjectMapper mapper = new ObjectMapper();
                String responseString = "";
                try {
                    responseString = mapper.writeValueAsString(responseResult);
                } catch (JsonProcessingException e) {
                    e.printStackTrace();
                }
                return responseString;
            }else {
                responseResult = ResponseResult.ok(o, url);
                return responseResult;
            }
        }
        return o;
    }
    
}

異常處理類

業務中經過拋出異常的方式來處理錯誤,在此處能夠全局處理spa

@RestControllerAdvice
public class ExceptionAdvice {
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * 出錯服務端口
     */
    @Value("${server.port}")
    private int servicePort;

    /**
     * 封裝異常
     *
     * @param req 請求
     * @param e   異常類
     * @return 返回封裝類
     */
    @ExceptionHandler(value = Exception.class)
    public ResponseResult jsonErrorHandler(HttpServletRequest req, Exception e) {
        logger.error(req.getRequestURL().toString(), e);
        Long host = null;
        //分配異常碼與host
        int status = ResponseStatusCode.UNKNOW_EXCEPTION;
        String msg = e.getMessage();
        if (e instanceof IOException || e instanceof CustomIOException) {
            status = ResponseStatusCode.IO_EXCEPTION;
            if (e instanceof CustomIOException){
                host = ((CustomIOException) e).getHost();
            }
        } else if (e instanceof NullPointerException || e instanceof CustomNullPointerException) {
            status = ResponseStatusCode.NULL_EXCEPTION;
            if (e instanceof CustomNullPointerException){
                host = ((CustomNullPointerException) e).getHost();
            }
        } else if (e instanceof SQLException || e instanceof CustomSQLException) {
            status = ResponseStatusCode.SQL_EXCEPTION;
            if (e instanceof CustomSQLException){
                host = ((CustomSQLException) e).getHost();
            }
        } else if (e instanceof ArgumentException) {
            status = ResponseStatusCode.ARGUMENT_EXCEPTION;
            if (((ArgumentException) e).getHost() != null && !((ArgumentException) e).getHost().equals(0)){
                host = ((ArgumentException) e).getHost();
            }
        } else if (e instanceof CustomException) {
            status = ((CustomException) e).getStatus();
            if (((CustomException) e).getHost() != null && !((CustomException) e).getHost().equals(0)){
                host = ((CustomException) e).getHost();
            }
        } else if (e instanceof UndeclaredThrowableException) {
            Throwable targetEx = ((UndeclaredThrowableException) e).getUndeclaredThrowable();
            if (targetEx != null) {
                msg = targetEx.getMessage();
            }
        }
        //獲取出錯服務ip
        int ip = 0;
        try {
            ip = Integer.valueOf(
                    Arrays.stream(
                            InetAddress.getLocalHost().getHostAddress()
                                    .split("\\.")).collect(Collectors.joining()));
        } catch (Exception e1) {
        }
        return ResponseResult.fail(
                status, msg, req.getRequestURL().toString(),
                host == null? Long.valueOf(String.valueOf(ip) + servicePort): host);
    }

}

接口響應處理類

上游服務得到下游服務的響應的處理類3d

public class ResponseHandler<T> {

    /**
     * 處理調用遠程接口的返回
     * @param responseResult
     * @return
     */
    public T handler(ResponseResult<?> responseResult) {
        int statusToken = responseResult.getStatus();
        String msg = responseResult.getMsg();
        Long host = responseResult.getHost();
        if (ResponseStatusCode.OK != statusToken){
            exceptionHandler(statusToken,msg, host);
        }

        return (T) responseResult.getData();
    }

    /**
     * 處理異常
     * @param statusToken 狀態碼
     * @param msg 錯誤消息
     * @param host 主機
     */
    private static void exceptionHandler(int statusToken, String msg, Long host) {
        if (ResponseStatusCode.IO_EXCEPTION == statusToken) {
            throw new CustomIOException(msg, host);
        } else if (ResponseStatusCode.NULL_EXCEPTION== statusToken) {
            throw new CustomNullPointerException(msg, host);
        } else if (ResponseStatusCode.SQL_EXCEPTION== statusToken) {
            throw new CustomSQLException(msg, host);
        } else if (ResponseStatusCode.ARGUMENT_EXCEPTION== statusToken) {
            throw new ArgumentException(msg, host);
        } else if (ResponseStatusCode.UNKNOW_EXCEPTION== statusToken) {
            throw new UnknowException(msg, host);
        } else {
            throw new CustomException(statusToken, msg, host);
        }
    }
}

接口例子

上面是全部服務都須要擁有的類,而後是幾個調用的小例子code

首先是幾個接口

@RestController
@RefreshScope
public class DcController {
    final
    ClientService clientService;

    @Autowired
    public DcController(ClientService clientService) {
        this.clientService = clientService;
    }
    
    @GetMapping("exception1")
    public String exception1() {
        throw new ArgumentException("錯誤");
    }

    @GetMapping("exception2")
    public int exception2() {
        throw new CustomException(250, "業務異常,自定義異常碼");
    }

    @GetMapping("exception3")
    public int exception3(){
        return new ResponseHandler<List<MetaVO>>().handler(clientService.generateList()).get(0).getDbid();
    }

    @GetMapping("list")
    public List<MetaVO> generateList() {
        List<MetaVO> list = new ArrayList<>();
        for (int i = 0; i < 2; i++) {
            MetaVO metaVO = new MetaVO();
            metaVO.setDbid(i);
            list.add(metaVO);
        }
        return list;
    }

}

而後是結果

clipboard.png

clipboard.png

clipboard.png

總結

以上就是spring cloud的全局封裝實踐,開發人員編寫業務邏輯不須要考慮返回的封裝,只須要考慮業務和自定義的狀態碼而已

相關文章
相關標籤/搜索