Better-Gateway 简单说明

Better-Gateway

如何配置?

Nacos 配置文件

serveice-providers.json 服务提供商总配置

[
  {
    "code": "01",
    "name": "fc-service-system",
    "dataId": "provider-01-config.json",
    "domainName": "fc-service-system",
    "authMethod": "token",
    "authConfig": {
      "ignorePath": [
        "/system/bankApi",
        "/system/user/grantProtocol",
        "/system/openApi/getCarWarning",
        "/system/api/external/callback/gps",
        "/chuzhi-app",
        "/chuzhi-web",
        "/system/institution/dealer/search",
        "/system/institution/dealer/search2",
        "/system/institution/dealer/modify",
        "/system/institution/dealer/search1",
        "/dadi-assets",
        "/system/user/login",
        "/open-api",
        "/openApi",
        "/xbcf",
        "/web",
        "/logConsole",
        "/kl-api",
        "/identifyVin",
        "/dd-api",
        "/xiehaibobobobo",
        "/cf-api",
        "/cz",
        "/system/order/pushElectronicDocPdfUrl"
      ],
      "ignorePathPrefix": [
        "/system/temp",
        "/system/openApi"
      ]
    },
    "type": "inner",
    "version": "5"
  }
]
  • code 服务提供商编码
  • name 服务提供商名称
  • dataId 服务提供商配置id
  • domainName 服务提供商域名
  • authMethod 默认认证方式
  • authConfig 额外认证方式
    • ignorePath 不认证的路径(全路径)
    • ignorePathPrefix 不认证的路径(前缀)
  • type 服务提供商类别
  • version 配置版本号

provider-01-config.json 服务提供商子配置

[
  {
    "url": "fc-service-system",
    "featCode": "001",
    "errorCodeList": [
      {
        "errorCode": "5106",
        "code": "001",
        "type": "P",
        "tips": "当前用户无此节点操作权限",
        "handleStrategy": "",
        "handleParam": ""
      }
    ]
  },
  {
    "url": "fc-service-system",
    "featCode": "002",
    "errorCodeList": [
      {
        "errorCode": "5012",
        "code": "001",
        "type": "P",
        "tips": "该项目未配置业务流程, 请联系项目管理员",
        "handleStrategy": "forward",
        "handleParam": "/system/institution/dealer/search"
      }
    ]
  },
  {
    "url": "fc-service-system",
    "featCode": "003",
    "errorCodeList": [
      {
        "errorCode": "4103",
        "code": "001",
        "type": "P",
        "tips": "保费不能大于保额",
        "handleStrategy": "retry",
        "handleParam": "3,3,true"
      }
    ]
  },
  {
    "url": "/system/institution/dealer/search",
    "featCode": "",
    "errorCodeList": [
      {
        "errorCode": "",
        "code": "",
        "type": "",
        "tips": "",
        "handleStrategy": "distribute",
        "handleParam": "http://10.98.14.80/system/institution/dealer/search"
      }
    ]
  }
]
  • url 接口地址(内部服务即服务名)
  • featCode 功能域编码
  • errorCodeList 错误码列表
    • errorCode 错误码(内部)
    • code 错误码(展示)
    • type 错误类型
    • tips 错误提示*
    • handleStrategy 处理策略**
    • handleParam 处理策略参数

默认情况下,错误提示会覆盖原始响应中的 msg 属性,可以通过占位符来进行扩展,目前支持的占位符有:

  • #APPCODE# 用于在 msg 中展示转换后的错误码
  • #MSG# 用于在 msg 中展示原始响应信息

分发策略 distribute 针对的是入站请求,与错误码映射无关。因此在配置分发策略时,url 为入站请求路径,其余错误码相关属性均设为空。

处理策略配置
  • 支持的策略
    • distribute 分发
      • handleStrategy distribute
      • handleParam 分发的目的地址,多个地址可用逗号隔开
    • forward 转发(暂不可用)
      • handleStrategy forward
      • handleParam 转发的目的地址
    • retry 重试(暂不可用)
      • handleStrategy retry
      • handleParam 重试次数,重试间隔

如何使用?

ApiResult

Better-Gateway 通过对响应的处理来匹配映射规则,因此系统内部服务需要使用统一的 ApiResult。

ApiResult 参考实现

@Data
public class ApiResult<T> implements Serializable {
    private static final long serialVersionUID = 0xc93480e15321b2c5L;

    private static final Logger logger = LoggerFactory.getLogger(ApiResult.class);

    /**
     * 状态码
     */
    private Integer code;

    /**
     * 说明信息
     */
    private String msg;

    /**
     * 错误code
     */
    private String appCode;

    /**
     * 服务名/url
     */
    private String path;

    /**
     * 返回数据
     */
    private T data;

    public ApiResult() {
        this.path = PropertyUtils.getServiceName();
    }

    public ApiResult(ResultCode code) {
        this.code = code.code();
        this.msg = code.message();
        this.appCode = String.valueOf(code.code());
        this.path = PropertyUtils.getServiceName();
    }

    public ApiResult(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
        this.appCode = String.valueOf(code);
        this.path = PropertyUtils.getServiceName();
    }

    public ApiResult(ResultCode resultCode, T data) {
        this.code = resultCode.getCode();
        this.msg = resultCode.getMsg();
        this.appCode = String.valueOf(resultCode.getCode());
        this.path = PropertyUtils.getServiceName();
        this.data = data;
    }

    public static <T> ApiResult<T> failure(Integer code, String path) {
        ApiResult<T> result = failure();
        result.setCode(code);
        result.setAppCode(String.valueOf(code));
        result.setPath(path);
        return result;
    }

    public static <T> ApiResult<T> failure(Integer code, String path, T data) {
        ApiResult<T> result = failure();
        result.setCode(code);
        result.setAppCode(String.valueOf(code));
        result.setPath(path);
        result.setData(data);
        return result;
    }

    public static ApiResult<String> failure(ResultCode code, String data, boolean isCustomErrorMessage) {
        ApiResult<String> result = failure(code, data);
        if (isCustomErrorMessage && Toolkit.isValid(data)) {
            result.setMsg(data);
        }
        return result;
    }

    public static <T> ApiResult<T> failure(APIException e) {
        ApiResult<T> result = failure();
        result.setAppCode(e.getAppCode());
        result.setPath(e.getPath());
        result.setMsg(e.getMsg());
        result.setData((T) e.getData());
        return result;
    }

    public static <T> ApiResult<T> success() {
        return new ApiResult<T>(ResultCode.SUCCESS);
    }

    public static <T> ApiResult<T> success(T data) {
        ApiResult<T> result = new ApiResult<T>(ResultCode.SUCCESS);
        result.setData(data);
        return result;
    }

    public static <T> ApiResult<T> success(T data, String msg) {
        ApiResult<T> result = new ApiResult<T>(ResultCode.SUCCESS.code(), msg);
        result.setData(data);
        return result;
    }

    public static <T> ApiResult<T> failure(ResultCode rc) {
        StackTraceElement se = Thread.currentThread().getStackTrace()[2];
        logger.error("failure result code: {}, msg: {}", rc.getCode(), rc.getMsg());
        logger.error("failure info: {} {} {}", se.getClassName(), se.getMethodName(), se.getLineNumber());
        return new ApiResult<T>(rc.getCode(), rc.getMsg());
    }

    public static <T> ApiResult<T> failure() {
        return failure(ResultCode.FAIL);
    }

    public static <T> ApiResult<T> failure(ResultCode code, T data) {
        ApiResult<T> result = failure(code);
        result.setData(data);
        return result;
    }
}

可以根据需要使用自定义的ApiResult,但需要有以下属性:

  • code:Integer 类型的状态码,原始形态,不影响原功能。**
  • appCode:String 类型的状态码,可以存入更丰富的信息,以及第三方接口的非 int 状态码。转换后的状态码会覆盖此值。
  • msg:提示信息,转换后的提示信息会覆盖此值。
  • path:异常路径。一般约定第三方接口出错为接口url,内部服务调用为服务名。也可采用其他命名,在配置文件中配置即可。

*区分 code 和 appCode 是为了不影响原接口,如果是 ApiResult 的 code 原本就是 String,直接使用即可,无需 appCode。

**当 code 不为成功时才会进行状态码映射,成功的状态配置在 common 包中的 ResultCode 枚举类。默认为200.

	/* 成功状态码 */
       SUCCESS(200, "success"),
服务间调用

微服务内部往往有多层调用链,发生异常的层级可能较深,但无论如何,需要将发生异常的服务路径或第三方接口地址 path 和异常状态码或第三方接口响应码 appCode 透传到网关,才能正常进行错误码映射。

为了方便处理服务间调用的响应,推荐使用 ApiResult 统一作为服务间调用的传递对象,原数据存入 ApiResult 的data 属性中。当最终调用者发生错误时,将出错的路径和状态码填入 ApiResult 中向上返回(此步骤可借助统一异常处理简化操作)。上层调用者拿到响应后先判断 code 是否为成功,成功则取出 data 正常处理,失败则继续向上抛出,直到网关。

一个栗子:客户端发出查询项目列表请求,网关将请求路由到 sys-service 处理, sys-service 需要通过 api-service 调用第三方接口 example.com 获取项目列表处理后响应给客户端。已知第三方接口成功响应中有status 为 000000。

首先,服务间调用需通过 ApiResult 传递,接口如下:

    @PostMapping(value = "/api/consumerFinance/project/searchProjectCode")
    ApiResult<JSONObject> searchProjectList(@RequestBody JSONObject request);

在最终调用者 api-service 中,如果判断第三方接口异常,则将其信息以 ApiResult 抛出(此处使用统一异常处理简化操作)。

    public ApiResult<JSONObject> queryApprove(SearchProductCodeRequest searchProductCodeRequest) throws Exception {
        // 省略请求组装
        try {
            result = httpUtil.doPost(QUERY_PROJECT_URL, paramMap.toJSONString()).getString();
            logger.info(result);
            json = JSONObject.parseObject(result);
            if (json != null) {
                String status = (String) json.get("status"); //接口返回状态码
                if ("000000".equals(status)) {
                    logger.info("操作成功");
                    return ApiResult.success(json.getJSONObject("data"));
                } else {
                    throw new APIException(status, QUERY_APPROVE_URL);
                }
            }
        } catch (Exception e) {
            logger.error("调用进件项目查询接口异常!", e);
        }
        throw new APIException(ResultCode.FAIL.getCode().toString(), QUERY_APPROVE_URL);
    }

在上层调用者 sys_service 中,判断 code 是否为成功,不成功继续向上抛出。

    // 省略业务代码
    ApiResult<JSONObject> apiResult = apiFeignService.searchProjectList(reqJO);
    if (!ResultCode.SUCCESS.getCode().equals(apiResult.getCode())) {
        throw new APIException(apiResult);
    }
    JSONObject data = apiResult.getData();
    // 省略业务代码

最终,网关可以根据 path 和 appCode 来进行状态码映射。

统一异常处理

为了方便使用,可以借助统一异常处理来抛出 ApiResult。如上文所用的 APIException 参考实现:

@Data
public class APIException extends RuntimeException {

    private String appCode;
    private String path;
    private String msg;
    private Object data;

    public APIException() {
    }

    public APIException(String appCode, String path) {
        this.appCode = appCode;
        this.path = path;
    }

    public APIException(String appCode, String path, Object data) {
        this.appCode = appCode;
        this.path = path;
        this.data = data;
    }

    public APIException(String appCode, String path, String msg, Object data) {
        super(msg);
        this.appCode = appCode;
        this.path = path;
        this.msg = msg;
        this.data = data;
    }

    public APIException(ApiResult apiResult) {
        this.appCode = apiResult.getAppCode();
        this.path = apiResult.getPath();
        this.msg = apiResult.getMsg();
        this.data = apiResult.getData();
    }
}

以及响应的异常处理:

@RestControllerAdvice
@Order(Ordered.HIGHEST_PRECEDENCE)
public class APIExceptionHandler {

    private static final Logger logger = LoggerFactory.getLogger(APIExceptionHandler.class);

    @ExceptionHandler(value = APIException.class)
    public ApiResult<Object> handleBizException(APIException e) {
        logger.error("发生接口调用异常 原因是:", e);
        ApiResult<Object> result = new ApiResult<>(ResultCode.FAIL);
        result.setAppCode(e.getAppCode());
        result.setPath(e.getPath());
        result.setMsg(e.getMsg());
        result.setData(e.getData());
        return result;
    }
}

以上服务内部处理并不固定,需要根据具体项目内部实现进行调整。总之需要将期望处理的异常信息透传到网关,网关才能进行错误码映射。

在对错误进行处理时,网关会记录本次请求的详细参数日志,以备后续处理:

[INFO] 2020-11-18 16:06:17.072 [reactor-http-nio-5] com.che300.gateway.filter.ErrorCodeFilter - 请求出现异常!请求Url [http://118.25.27.178:8080/system/dict/getPageUseDict] 请求头 [{Host=[118.25.27.178:8080], User-Agent=[Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:82.0) Gecko/20100101 Firefox/82.0], Accept=[application/json], Accept-Language=[zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2], Accept-Encoding=[gzip, deflate], Referer=[http://fezz.ceshi.che300.com/ccic/], Content-Type=[application/x-www-form-urlencoded; charset=utf-8], Authorization=[459a686a52b445b4a3b8516827d368aa], Origin=[http://fezz.ceshi.che300.com], Content-Length=[28], DNT=[1], Connection=[keep-alive], Pragma=[no-cache], Cache-Control=[no-cache], currentUserInfo=[{"userRoles":"1,6,9,10,11,19,20,21,22,23,24,25,26,27,28,29,41,51,52,53,121,123","institutionId":"11010000","dataAuth":2,"userId":1,"account":"0000000000","token":"459a686a52b445b4a3b8516827d368aa"}]}] 请求内容 [codes=product_type%2Cenabled] 响应内容 [{"code":1000,"msg":"/ by zero","appCode":"2001","path":"fc-service-api","data":null}]

如何定制?

网关处理逻辑图

配置文件解析

在项目启动时,网关会加载服务提供商配置和全量的提供商子配置信息。

在服务提供商配置发生变更时,网关会更新服务提供商配置和version发生变更的提供商子配置信息。

如果需要监听配置的变更,从中解析出关心的配置信息,需要实现DataFormatConversion接口:并将其注册进Spring容器中

public interface DataFormatConversion {

    /**
     * 更新服务提供商
     *
     * @param serviceProviderList
     */
    void updateServiceProvider(JSONArray serviceProviderList);

    /**
     * 更新状态码
     *
     * @param dataId
     * @param statusCodeList
     */
    void updateStatusCode(String dataId, JSONArray statusCodeList);
}

当配置发生变更时,所有实现此接口的类中的方法均会被调用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值