java农业银行-企业银行ERP接口开发(4-接口对接)(批量代发结果查询)

 没看我之前的文章的先看一下《前期准备》,再来看这篇文章java农业银行-企业银行ERP接口开发(1-前期准备)icon-default.png?t=N7T8https://blog.csdn.net/new_public/article/details/133882741

 这篇文章我们主要讲:

批平台处理结果查询(IBAQ06)


最近一直想出这篇文章,奈何被一条sql搞了半个月,sql极其复杂,条件无敌多,最让人难受的是,开发环境没有数据,只有测试环境有,公司又不给我测试环境数据库的链接,搞得我看不了sql解释计划,不知道是走没走索引,只能调一下又上测试环境,结果出问题,又继续调.....


回归正题,这篇文章主要讲调用农行批量代发接口后,对代发结果进行查询。

先看请求报文,没有什么太多的特有字段,只有一个交易请求流水号。

这个流水号就是调用批量代发接口的时候,传的流水号<ReqSeqNo>请求方流水号</ReqSeqNo>,所以我在批量汇总代发接口文章里面强调把这个流水号记录到库表,后期用来查询交易结果的。

根据请求报文创建一个实体对象,继承公共请求对象(公共请求对象去第一篇文章拿)。

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

/**
 * IBAQ06(批平台处理结果查询)请求报文字段
 * */
@XmlRootElement(name = "ap")
public class IBAQ06RequestDTO extends RequestBaseEntity {

    /**
     * ???
     * */
    @XmlElement(name = "Corp")
    private Corp corp;

    @XmlAccessorType(XmlAccessType.FIELD)
    public static class Corp {

        /**
         * 原批交易请求流水号
         * */
        @XmlElement(name = "CustomNo")
        private String customNo;

        public String getCustomNo() {
            return customNo;
        }

        public void setCustomNo(String customNo) {
            this.customNo = customNo;
        }
    }

    public Corp getCorp() {
        return corp;
    }

    public void setCorp(Corp corp) {
        this.corp = corp;
    }
}

然后看接口的应答报文

有没有注意到,标签名竟然是中文,好家伙,我都震惊了。

根据应答报文建立应答报文实体对象(里面有个文件对象,是用来解析应答报文文件的时候用的。由于查询IBAF04交易结果和查询IBBF23交易结果的返回文件里面的字段不一致,所以定义了两个文件对象,一个是IBAF04FileObject,一个是IBBF23FileObject,后面处理查询结果的时候,要区别一下是发起哪个接口进行交易的,从而选择不同的文件对象进行解析),并继承公共应答报文对象。get set 方法自己加,或者打个Lombok注解。



import javax.xml.bind.annotation.*;
import java.util.List;

/**
 * IBAQ06(批平台处理结果查询)应答报文字段
 * */
@XmlRootElement(name = "ap")
@XmlAccessorType(XmlAccessType.FIELD)
public class IBAQ06ResponseDTO extends ResponseBaseEntity {

    /**
     * 原批交易类别(批交易码)
     * */
    @XmlElement(name = "TransType")
    private String transType;

    /**
     * ????
     * */
    @XmlElement(name = "BatchPlat")
    private BatchPlat batchPlat;

    /**
     * IBAF04本行应答报文返回的文件对象
     * */
    @XmlTransient
    private List<IBAF04FileObject> IBAF04FileObjectList;

    /**
     * IBBF23他行应答报文返回的文件对象
     * */
    @XmlTransient
    private List<IBBF23FileObject> IBBF23FileObjectList;

    @XmlAccessorType(XmlAccessType.FIELD)
    public static class BatchPlat {

        /**
         * 批处理
         * */
        @XmlElement(name = "批处理")
        private BatchHandler batchHandler;

        /**
         * 批处理信息类
         * */
        @XmlAccessorType(XmlAccessType.FIELD)
        public static class BatchHandler {
            /**
             * 状态
             * */
            @XmlElement(name = "状态")
            private String status;

            /**
             * 子状态
             * */
            @XmlElement(name = "子状态")
            private String substate;

            /**
             * 描述
             * */
            @XmlElement(name = "描述")
            private String describe;

            /**
             * 成功金额
             * */
            @XmlElement(name = "成功金额")
            private String successAmt;

            /**
             * 成功笔数
             * */
            @XmlElement(name = "成功笔数")
            private String successNum;

            /**
             * 失败金额
             * */
            @XmlElement(name = "失败金额")
            private String failAmt;

            /**
             * 失败笔数
             * */
            @XmlElement(name = "失败笔数")
            private String failNum;

            /**
             * 异常金额
             * */
            @XmlElement(name = "异常金额")
            private String exceptionAmt;

            /**
             * 异常笔数
             * */
            @XmlElement(name = "异常笔数")
            private String exceptionNum;

            /**
             * 落地金额
             * */
            @XmlElement(name = "落地金额")
            private String groundAmt;

            /**
             * 落地笔数
             * */
            @XmlElement(name = "落地笔数")
            private String groundNum;

        }

    }

    // 文件对象
    public static class IBAF04FileObject {
        /**
         * 客户编号
         * */
        private String customerCode;

        /**
         * 收款人姓名
         * */
        private String crAccName;

        /**
         * 收款人账号
         * */
        private String crBankNo;

        /**
         * 币种
         * */
        private String cur;

        /**
         * 账簿号
         * */
        private String accountBookCode;

        /**
         * 交易金额
         * */
        private Double amt;

        /**
         * 摘要
         * */
        private String excerpt;

        /**
         * 合约校验标志 01-强制校验 02-均不校验
         * */
        private String contractFlag;

        /**
         * 户名校验标志 0-否 1-是
         * */
        private String accountFlag;

        /**
         * 客户移动电话校验标识 0-否 1-是
         * */
        private String phoneFlag;

        /**
         * 移动电话
         * */
        private String phone;

        /**
         * 注册证件类型代码
         * */
        private String idCardType;

        /**
         * 证件号码
         * */
        private String idCardCode;

        /**
         * 处理结果
         * */
        private String handlerResult;

        /**
         * 返回码
         * */
        private String responseCode;

        /**
         * 返回码中文描述
         * */
        private String responseCodeDesc;

    }

    // 文件对象
    public static class IBBF23FileObject {

        /**
         * 交易序号
         * */
        private String tradeNo;

        /**
         * 客户请求序号
         * */
        private String customerCode;

        /**
         * 系统流水号
         * */
        private String systemSerialNo;

        /**
         * 交易日期
         * */
        private String tradeDate;

        /**
         * 交易类型
         * */
        private String tradeType;

        /**
         * 交易金额
         * */
        private Double amt;

        /**
         * 处理结果
         * */
        private String handlerResult;

        /**
         * 结果描述
         * */
        private String resultDesc;

        /**
         * 对方户名
         * */
        private String crAccName;

        /**
         * 对方账号
         * */
        private String crBankNo;

        /**
         * 用途
         * */
        private String useTo;

    }

}

来到业务场景,调用批量代发后,记录请求流水号,后面定时去刷交易结果就行了,大概发起后十分钟就可以去刷了,具体看批量付款的收款对象有多少个吧,太多就要等得久一些,前期测试一个收款账户,失败的话,两三分钟内出结果,成功的话可能得四五分钟这样。


AbcErpToIctSocket农行请求工具类(去第二篇文章,有专门解释,这里不解释这个类了)增加一个方法,专门用来查询批量代发结果的。

    /**
     * IBAQ06 批平台处理结果查询
     * @transCode 批量代发交易接口码 IBAF04 | IBBF23
     * */
    public Map<String, Object> ibaq06(IBAQ06RequestDTO requestEntity, String transCode) {
        if (requestEntity == null) {
            log.error("IBAQ06报文请求对象不能为空!");
            return null;
        }
        requestEntity.setCctransCode(CctransCodeEnum.IBAQ06.code());
        requestEntity.setIsEncryption("0");
        // 开始socket请求
        Map<String, Object> socketResponseMap =  socket(requestEntity, IBAQ06ResponseDTO.class);
        // 解析报文返回的文件对象
        Object responseObject = socketResponseMap.get("responseEntity");
        if (responseObject != null) {
            IBAQ06ResponseDTO ibaq06Response = (IBAQ06ResponseDTO) responseObject;
            // 本行
            if (StringUtils.equals(transCode, "IBAF04")) {
                List<IBAQ06ResponseDTO.IBAF04FileObject> fileObjectList = new ArrayList<>();
                this.analysisFileObject(ibaq06Response, fileObjectList, IBAQ06ResponseDTO.IBAF04FileObject.class);
                if (!CollectionUtils.isEmpty(fileObjectList)) {
                    ibaq06Response.setIBAF04FileObjectList(fileObjectList);
                }
            } else if (StringUtils.equals(transCode, "IBBF23")) {
                // 他行
                List<IBAQ06ResponseDTO.IBBF23FileObject> fileObjectList = new ArrayList<>();
                this.analysisFileObject(ibaq06Response, fileObjectList, IBAQ06ResponseDTO.IBBF23FileObject.class);
                if (!CollectionUtils.isEmpty(fileObjectList)) {
                    ibaq06Response.setIBBF23FileObjectList(fileObjectList);
                }
            }
        }
        return socketResponseMap;
    }

上面的socket方法去第二篇文章看,这里不说了,这里重点说analysisFileObject方法,这个是用来解析应答文件的。

    /**
     * 解析应答报文返回的文件
     * @responseEntity 应答报文对象
     * @fileObjectList 文件对象List,用来装载已经解析好的对象
     * @fileObjectClass 文件对象class
     * */
    private  <R extends ResponseBaseEntity> void analysisFileObject(R responseEntity, List fileObjectList, Class fileObjectClass) {
        // 如果有文件标识,解析文件
        if (responseEntity != null && responseEntity.getFileFlag() != null && responseEntity.getFileFlag() == 1) {
            // 文件名
            String batchFileName = responseEntity.getCmp().getBatchFileName();
            // 取出文件类所有字段
            Field[] fields = fileObjectClass.getDeclaredFields();
            // 字段转成数组, 值为字段名
            String[] respHeadArray = new String[fields.length];
            for (int index = 0; index < fields.length; index++) {
                respHeadArray[index] = fields[index].getName();
            }
            if (StringUtils.isNotBlank(batchFileName)) {
                // 组合文件全路径
                String fileFullPath = new StringBuffer(ictFileDownloadPath).append("/").append(batchFileName).toString();
                File file = new File(fileFullPath);
                // 如果文件存在
                if (file.exists()) {
                    try {
                        // 读取文件  FileUtils 是 org.apache.commons.io 包下的
                        String fileContent = FileUtils.readFileToString(file, "GBK");
                        // 如果文件内容有值
                        if (StringUtils.isNotBlank(fileContent)) {
                            // 文件内容转成数组
                            String[] contentArray = fileContent.split("\\n");
                            ObjectMapper objectMapper = new ObjectMapper();
                            // 文件里面每一行各个文件的分隔符
                            String fieldSeparator = "\\|\\_\\|";
                            // 遍历文件内容
                            for (int contentIndex = 0; contentIndex < contentArray.length; contentIndex++) {
                                Map<String, String> map = new HashMap<>();
                                // 把每一行的值转成数组,后面拼一个空格,避免等会转换成数组的时候,如果后面都是|_||_|,会丢失后面的几个空字符串
                                String[] contentValueArray = new StringBuffer(contentArray[contentIndex]).append(" ").toString().split(fieldSeparator);
                                // 循环设置字段值
                                for (int i = 0; i < respHeadArray.length; i++) {
                                    // uncapitalize 首字母小写 StringUtils 是org.apache.commons.lang3 包下的
                                    map.put(StringUtils.uncapitalize(respHeadArray[i]), contentValueArray[i]);
                                }
                                // map转成实体
                                fileObjectList.add(objectMapper.convertValue(map, fileObjectClass));
                            }
                        }
                    } catch (IOException e) {
                        log.error("应答报文MFS文本文件解析失败", e);
                    }
                }
            }
        }
    }

接下来就是测试了,new一个请求报文对象,并设置交易流水号即可。

        // 记得注入 AbcErpToIctSocket 工具类
        // @Autowired
        // private AbcErpToIctSocket abcErpToIctSocket;
        IBAQ06RequestDTO ibaq06Request = new IBAQ06RequestDTO();
        IBAQ06RequestDTO.Corp corp = new IBAQ06RequestDTO.Corp();
        corp.setCustomNo("PO23111316564908xxxx");
        ibaq06Request.setCorp(corp);
        Map<String, Object> stringObjectMap = abcErpToIctSocket.ibaq06(ibaq06Request);

请求报文是这样子的

0285   
<ap>
    <CCTransCode>IBAQ06</CCTransCode>
    <ProductID>ICC</ProductID>
    <ChannelType>ERP</ChannelType>
    <CorpNo></CorpNo>
    <OpNo></OpNo>
    <AuthNo></AuthNo>
    <ReqSeqNo></ReqSeqNo>
    <ReqDate>20231122</ReqDate>
    <ReqTime>134006</ReqTime>
    <Sign></Sign>
    <Corp>
        <CustomNo>PO23111316564908xxxx</CustomNo>
    </Corp>
</ap>

应答报文是这样子的,由于填写信息不正确,所以失败了。

0695   <ap>
  <Cmp>
    <RespPrvData></RespPrvData>
    <BatchFileName></BatchFileName>
    <CmeSeqNo>SS20231122134000xxxx</CmeSeqNo>
  </Cmp>
  <BatchPlat>
    <批处理>
      <状态>1</状态>
      <描述>CFRC48失败[EAPAYS1003-调用boe失败!-[rtbasic]输入过渡账户【273201*****0786】有误!]</描述>
    </批处理>
  </BatchPlat>
  <CCTransCode>IBAQ06</CCTransCode>
  <RespDate>20231122</RespDate>
  <RespTime>134008</RespTime>
  <RespSeqNo></RespSeqNo>
  <RespSource>0</RespSource>
  <RespCode>0000</RespCode>
  <RespInfo>交易成功</RespInfo>
  <RxtInfo>批量交易处理失败!</RxtInfo>
  <Cme>
    <RecordNum>0</RecordNum>
    <FieldNum>0</FieldNum>
  </Cme>
  <FileFlag>0</FileFlag>
</ap>

对于应答报文的处理,我是这么写的,对应业务代码自己补齐。

// 记得注入 AbcErpToIctSocket 工具类
// @Autowired
// private AbcErpToIctSocket abcErpToIctSocket;

IBAQ06RequestDTO ibaq06Request = new IBAQ06RequestDTO();
IBAQ06RequestDTO.Corp corp = new IBAQ06RequestDTO.Corp();
corp.setCustomNo("PO23111316564908xxxx");
ibaq06Request.setCorp(corp);
Map<String, Object> responseObjectMap = abcErpToIctSocket.ibaq06(ibaq06Request);
// 查询结果前期判断
if (this.socketRequestAfterDetermine(responseObjectMap)) {
    // 如果应答对象有值
    if (responseObjectMap.get("responseEntity") != null) {
        IBAQ06ResponseDTO ibaq06Response = (IBAQ06ResponseDTO) responseObjectMap.get("responseEntity");

        IBAQ06ResponseDTO.BatchPlat.BatchHandler batchHandler = ibaq06Response.getBatchPlat().getBatchHandler();

        if (StringUtils.equals(batchHandler.getStatus(), "H")) {
            // 批处理状态为H,则说明正在处理中,稍后再查询

        } else if (StringUtils.equals(batchHandler.getStatus(), "1")) {
            // 批处理状态为1,则说明整批失败(全部失败)

        } else if (StringUtils.equals(batchHandler.getStatus(), "5")) {
            // 批处理状态为5,则说明交易处理完成

            // 如果落地笔数或异常笔数都没有,则说明整批都交易完成,下次不用再定时查询了
            if (StringUtils.equals(batchHandler.getExceptionNum(), "0") &&
                    StringUtils.equals(batchHandler.getGroundNum(), "0")) {
                
            }
            // 设置成功笔数,成功金额
            Double.valueOf(batchHandler.getSuccessAmt());
            Integer.parseInt(batchHandler.getSuccessNum());
            // 失败金额,失败笔数
            Double.valueOf(batchHandler.getFailAmt());
            Integer.parseInt(batchHandler.getFailNum());

            // 处理每条收款信息的结果,fileObjectList 是前面根据应答文件名获取文件解析后获得的,里面是每一个收款账户的收款信息以及交易状态
            // 这里必须要确定前面是哪个接口发起的批量交易,这就要在发起批量交易时,记录交易码到数据库里面了
            List fileObjectList = null;
            // 本行
            if (StringUtils.equals(批量代发接口交易码, "IBAF04")) {
                fileObjectList = ibaq06Response.getIBAF04FileObjectList();
            } else {
                // 他行
                fileObjectList = ibaq06Response.getIBBF23FileObjectList();
            }
            if (!CollectionUtils.isEmpty(fileObjectList)) {
                fileObjectList.forEach(fileObject -> {
                    IBAQ06ResponseDTO.IBAF04FileObject ibaf04FileObject = null;
                    IBAQ06ResponseDTO.IBBF23FileObject ibbf23FileObject = null;
                    // 本行
                    if (StringUtils.equals(批量代发接口交易码, "IBAF04")) {
                        ibaf04FileObject = (IBAQ06ResponseDTO.IBAF04FileObject) fileObject;
                    } else {
                        // 他行
                        ibbf23FileObject = (IBAQ06ResponseDTO.IBBF23FileObject) fileObject;
                    }
                    // 客户编号   这里说一下,这个客户编号是我们批量代发的时候设置上的,这时候会原样返回,方便我们用于回写业务数据
                    String sourceId = ibaf04FileObject != null ? ibaf04FileObject.getCustomerCode() : ibbf23FileObject.getCustomerCode();
                    // 处理结果
                    String handlerResult = ibaf04FileObject != null ? ibaf04FileObject.getHandlerResult() : ibbf23FileObject.getHandlerResult();
                    // 判断这条记录是成功了还是失败 5-失败、E-不合法 这两种状态为明确失败;4-成功 为明确成功;其他状态为状态不确定,等下次查询
                    // 只有明确是成功还是失败了,再根据sourceId更新业务数据
                    if (IBAQ06HandlerStatusEnum.Fail.code().equals(handlerResult) ||
                            IBAQ06HandlerStatusEnum.FailE.code().equals(handlerResult) ||
                            IBAQ06HandlerStatusEnum.Success.code().equals(handlerResult)) {
                        
                        // 每条收款人信息的交易返回码,以及返回码描述,可能是成功信息,也可能是失败原因
                        if (ibaf04FileObject != null) {
                            ibaf04FileObject.getResponseCode();
                            ibaf04FileObject.getResponseCodeDesc();
                        } else {
                            ibbf23FileObject.getResultDesc();
                        }
                    }
                });
            }
        }
    }
}

socketRequestAfterDetermine 请求后处理方法

/**
* 交易结果查询后,前期通用判断
* @resMap socket请求结果
* @return 是否请求成功
* */
// 有些代码我删掉了,自己完善
private boolean socketRequestAfterDetermine(Map<String, Object> resMap) {
    if (resMap == null) {
        // 查询出错
        return false;
    } else if (resMap.get("errorMessage") != null) { // 有错误信息

        resMap.get("errorMessage").toString();

        return false;
    } else if (resMap.get("isAlreadyResq") == null) { // 没有发送socket请求

        return false;
    } else if (resMap.get("responseEntity") == null) { // 应答报文对象

        if (resMap.get("responseMessage") != null) {
            resMap.get("responseMessage").toString();
        }

        return false;
    }
    return true;
}

这里啰嗦一下,事务最好还是手动开启,每查一次交易结果,就开启一次事务,最后处理完业务数据后就提交事务,然后再开启事务查下一个批量代发交易结果,把它们都分开成独立的一样,因为下一次的查询跟前面的没有任何关系。 


枚举类

/**
 * IBAQ06 批量支付结果查询响应文件里面每条记录的具体支付状态
 * */
public enum IBAQ06HandlerStatusEnum {

    Success("4", "成功"),
    Fail("5", "失败"),
    FailE("E", "失败");
    // 其他状态,为不确定状态

    private String code;

    private String title;

    IBAQ06HandlerStatusEnum(String code, String title) {
        this.code = code;
        this.title = title;
    }

    public String code() {
        return this.code;
    }

    public String title() {
        return this.title;
    }
}

好了,又是上班时间写文章,得抓紧干活了,不然明天就被骂了。

码字不易,于你有利,勿忘点赞。

一点飞鸿影下,青山绿水,白草红叶黄花

  • 7
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
农业银行 ERP 接口开发是指开发适用于农业银行企业资源规划(ERP)系统的接口ERP 系统可以帮助银行实现信息的集成、业务流程的优化和效率的提升。接口开发主要是为了与其他系统进行数据的互通和交换。 在农业银行 ERP 接口开发过程中,首先需要进行需求分析和系统设计。根据银行的具体需求,确定需要开发接口功能、数据格式、传输方式等。接下来,开发人员根据设计方案进行编码和测试,确保接口的正确性和稳定性。 银行 ERP 接口开发的关键是与现有系统的对接农业银行可能拥有多个系统,如核心银行系统、账户管理系统、风险管理系统等。在开发过程中,需要与这些系统进行数据的交换和同步。因此,开发人员需要熟悉银行系统的架构和接口规范,并与相关系统的开发人员进行紧密合作。 另外,农业银行 ERP 接口开发还需要考虑数据的安全性和稳定性。银行作为金融机构,对数据的保密和可靠性要求非常高。因此,在接口开发过程中,需要采取合适的安全措施,如身份验证、数据加密和防止数据篡改等。同时,开发人员还需要测试接口在各种情况下的稳定性和扩展性,以确保接口能够在高负载和异常情况下正常工作。 综上所述,农业银行 ERP 接口开发是一个复杂而重要的任务。它需要开发人员具备深入的银行业务和系统知识,同时还要有丰富的编码和测试经验。只有通过合理的需求分析、系统设计和与相关系统的对接,才能开发出满足农业银行需求的稳定、安全的接口
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值