概述
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 forwardhandleParam 转发的目的地址
retry 重试(暂不可用)handleStrategy retryhandleParam 重试次数,重试间隔
- distribute 分发
如何使用?
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);
}
当配置发生变更时,所有实现此接口的类中的方法均会被调用。
最后
以上就是怕孤单指甲油为你收集整理的Better-Gateway 简单说明的全部内容,希望文章能够帮你解决Better-Gateway 简单说明所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复