Spring Cloud的全局封装实践

码界逸云
• 阅读 3391

前言

跨应用的全局封装通过模仿java的异常抛出实现,通过各个服务使用相同的接口返回格式实现服务消息的传递,在规范化的同时快速定位真正出现问题的服务。

全局接口返回格式分为外部接口格式和内部接口格式,这里简化为内外接口格式一致。

整体流程如下:

Spring Cloud的全局封装实践

全局返回封装类

整个spring cloud服务的统一接口

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;
    }
}

状态码常量类

这个类为整体服务定义的状态码,可以使用枚举实现

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;
}

自定义异常类

自定义异常类继承RuntimeException

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响应处理类,使用全局封装类封装返回

@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;
    }
    
}

异常处理类

业务中通过抛出异常的方式来处理错误,在此处可以全局处理

@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);
    }

}

接口响应处理类

上游服务获得下游服务的响应的处理类

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);
        }
    }
}

接口例子

上面是所有服务都需要拥有的类,然后是几个调用的小例子

首先是几个接口

@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;
    }

}

然后是结果

Spring Cloud的全局封装实践

Spring Cloud的全局封装实践

Spring Cloud的全局封装实践

总结

以上就是spring cloud的全局封装实践,开发人员编写业务逻辑不需要考虑返回的封装,只需要考虑业务和自定义的状态码而已

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
美凌格栋栋酱 美凌格栋栋酱
7个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Stella981 Stella981
3年前
C# Aspose.Cells导出xlsx格式Excel,打开文件报“Excel 已完成文件级验证和修复。此工作簿的某些部分可能已被修复或丢弃”
报错信息:最近打开下载的Excel,会报如下错误。(xls格式不受影响)!(https://oscimg.oschina.net/oscnet/2b6f0c8d7f97368d095d9f0c96bcb36d410.png)!(https://oscimg.oschina.net/oscnet/fe1a8000d00cec3c
Easter79 Easter79
3年前
SpringBoot自定义序列化的使用方式
场景及需求:项目接入了SpringBoot开发,现在需求是服务端接口返回的字段如果为空,那么自动转为空字符串。例如:\    {        "id":1,        "name":null    },    {        "id":2,        "name":"x
Wesley13 Wesley13
3年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Stella981 Stella981
3年前
Django之Django模板
1、问:html页面从数据库中读出DateTimeField字段时,显示的时间格式和数据库中存放的格式不一致,比如数据库字段内容为2012082616:00:00,但是页面显示的却是Aug.26,2012,4p.m.答:为了页面和数据库中显示一致,需要在页面格式化时间,需要添加<td{{dayrecord.p\_time|date:
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Stella981 Stella981
3年前
HIVE 时间操作函数
日期函数UNIX时间戳转日期函数: from\_unixtime语法:   from\_unixtime(bigint unixtime\, string format\)返回值: string说明: 转化UNIX时间戳(从19700101 00:00:00 UTC到指定时间的秒数)到当前时区的时间格式举例:hive   selec
Wesley13 Wesley13
3年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Stella981 Stella981
3年前
SpringBoot自定义序列化的使用方式
场景及需求:项目接入了SpringBoot开发,现在需求是服务端接口返回的字段如果为空,那么自动转为空字符串。例如:\    {        "id":1,        "name":null    },    {        "id":2,        "name":"x