掘金 后端 ( ) • 2024-04-24 11:25

昨天,一个上线已久的供应链金融项目出现异常,经过一整天的排查,终于定位了两个问题的原因,并通过重启服务解决了其中的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模块的本地代码重新部署上线,即可解决该问题。至于为什么生产上的代码与本地代码不一致,有可能是代码封板之后,本地修正了代码,但是忘记发布了。