昨天,一个上线已久的供应链金融项目出现异常,经过一整天的排查,终于定位了两个问题的原因,并通过重启服务解决了其中的1个问题,另一个问题需要发起上线流程,通过上线才能解决。这两个问题,分别命名为【消失的字段】和【消失的消息】。在正式阐述问题之前,先描绘一下系统概况,以便读者更容易理解文中描述的问题排查逻辑,问题排查逻辑是这两篇文章的重点。
上图虚线框内是供应链金融项目的服务,包括:银行对接服务(bank-adapter)、贷前服务(preloan)、贷中服务(onloan)和贷后服务(postloan),bank-adapter服务属于外联服务,主要和银行进行对接。
橙色线条表示业务服务(贷前、贷中和贷后)通过银行对接服务bank-adapter向银行主动发起查询,包括查询:企业准入结果、授信结果、融资申请结果、放款结果和还款结果。
绿色线条表示银行主动通过银行对接服务bank-adapter向供应链金融平台推送消息,bank-adapter服务收到消息进行校验解码之后发送到消息队列MQ,业务服务(贷前、贷中和贷后)监听消息队列实时获取最新的推送消息,进行业务处理。
目前系统出现了两个问题,一个是业务服务主动请求融资申请结果,发现没有审批额度信息——【消失的字段】;另一个是贷中服务和贷后服务无法从消息队列获取最新的消息——【消失的消息】。
本文主要描述【消失的字段】问题以及问题排查过程,并给出问题解决方案。
一、问题描述
供应链金融平台用户通过页面操作发起融资申请结果查询,发现始终无法刷新列表的审批额度信息,同时页面没有报错提示信息。
二、问题排查(按照橙色服务请求路线进行排查)
1、前端页面响应数据查看。在用户当前操作页面,按F12打开浏览器调试窗口,点击【Network】tab,清空当前网络请求,再次执行融资申请结果查询操作,此时在浏览器调试窗口可以看到对应的网络请求,查看Response,可以看到后端接口返回的响应结果中确实没有审批额度信息。此时,可以将问题范围缩小到后端服务。
2、模拟前端请求。构造参数,通过POSTMAN对生产的融资申请结果查询接口发起请求,该接口由bank-adapter服务提供,返回结果显示如下:
{
"bizno": "FS2023092610422152",
"bankbizno": "2023092603412496",
"custcode": "9131011075800000T",
"custname": "世界第一有限公司",
"reply": "1",
"refusemsg": "",
"clamt": 1537451.29,
"loanrate": null,
"approtime": "20230926",
"currency": "CNY",
"busstype": "01",
"remark": "",
"code": "200",
"message": "交易成功"
}
疑问来了,明明bank-adapter接口返回了审批金额(对应字段为clamt)信息,为什么贷中服务返回给前端的结果却没有审批金额字段呢?返回结果从bank-adapter服务传递给onloan服务的过程中出现了什么问题?
3、查看bank-adapter和onloan服务的日志,因为从代码里对服务间调用的请求和响应记录了日志。通过日志发现,银行返回给bank-adapter服务的结果如下:
{
"channelNo": "32060201",
"bussType": "01",
"efinanceNo": "2023092603412496",
"bussNo": "FS2023092610422152",
"custName": "世界第一有限公司",
"custCode": "9131011075800000T",
"reply": "1",
"refuseMsg": "",
"currency": "CNY",
"clAmt": 1537451.29,
"loanRate": null,
"approTime": "20230926",
"remark": ""
}
贷中服务onloan将从bank-adapter收到的响应结果输出如下:
{
"reply":"1",
"currency":"CNY",
"remark":"",
"code":"200",
"message":"交易成功"
}
通过对比上述两个结果,发现:驼峰形式命名的字段全部消失了!!!另外,通过第2步的结果与bank-adapter服务日志输入结果对比发现:bank-adapter服务接口最终输出的结果字段都被转为小写字段了,而且有字段名不一致的情况。可以断定bank-adapter收到银行返回的结果之后,做了一些逻辑处理。接下来看代码!
4、打开bank-adapter服务融资申请结果查询结果的代码,发现最后将银行返回的数据构建为一个ApplyResultResp类型对象。
@Data
public class ApplyResultResp {
@JSONField(name = "bussNo")
private String bussNo;
@JSONField(name = "efinanceNo")
private String efinanceNo;
@JSONField(name = "custCode")
private String custCode;
@JSONField(name = "custName")
private String custName;
@JSONField(name = "reply")
private String reply;
@JSONField(name = "refuseMsg")
private String refuseMsg;
@JSONField(name = "clAmt")
private BigDecimal clAmt;
@JSONField(name = "loanRate")
private BigDecimal loanRate;
@JsonFormat(pattern = "yyyyMMdd")
@JSONField(name = "approTime")
private Date approTime;
@JSONField(name = "currency")
private String currency;
@JSONField(name = "bussType")
private String bussType;
@JSONField(name = "remark")
private String remark ;
private String code;
private String message;
}
具体转换代码使用fastjson工具进行操作:
ApplyResultResp applyResultResp = JSONObject.parseObject(jsonResultFromBank, ApplyResultResp.class);
从上述代码来看,不应该出现银行返回结果与bank-adapter接口响应结果不一致的情况,大胆猜测:生产上的代码与本地代码不一致。于是,请运维工程师从生产环境将容器中运行的bank-adapter.jar包拉下来,通过反编译查看ApplyResultResp类的代码,发现果然!反编译显示该类的代码如下:
public class ApplyResultResp {
@JSONField(name = "bussNo")
private String bizno;
@JSONField(name = "efinanceNo")
private String bankbizno;
@JSONField(name = "custCode")
private String custcode;
@JSONField(name = "custName")
private String custname;
@JSONField(name = "reply")
private String reply;
@JSONField(name = "refuseMsg")
private String refusemsg;
@JSONField(name = "clAmt")
private BigDecimal clamt;
@JSONField(name = "loanRate")
private BigDecimal loanrate;
@JsonFormat(pattern = "yyyyMMdd")
@JSONField(name = "approTime")
private Date approtime;
@JSONField(name = "currency")
private String currency;
@JSONField(name = "bussType")
private String busstype;
@JSONField(name = "remark")
private String remark;
private String code;
private String message;
... getter and setter ...
}
由此看出,生产环境bank-adapter服务将银行返回的结果字段全部转为了小写,然后onloan服务收到这些小写化之后的响应结果,通过BeanUtils.copyProperties方法构建响应对象,而onloan服务的响应对象字段也是驼峰形式的,所以构建过程中,驼峰形式字段信息全部丢失。
三、解决方案
将bank-adapter模块的本地代码重新部署上线,即可解决该问题。至于为什么生产上的代码与本地代码不一致,有可能是代码封板之后,本地修正了代码,但是忘记发布了。