########### 这些代码都是很早之前写的了,如果有需要的朋友,有些文章(1到4)看一下流程和说明就行了,然后代码用文章专栏的【后续-代码改造】,不过有些代码还是需要用回之前写的,比如【实体】【枚举】【工具类】等,通信方法和逻辑处理就不推荐用【(2-接口对接)(汇兑-单笔对公、对私)】【(3-接口对接)(汇总批量代发)】【​​​​​​​(4-接口对接)(批量代发结果查询)】这几篇文章里面的了###########


公司突然要对接农行的企业银行接口,实现给员工费用报销,充值退款等操作,现记录一下,避免下次开发不记得,我用的是java开发,sockte请求方式对接,全程基于我个人理解,有些地方可能写得不对,多多见谅


开发的接口如下(随便抽一两个说说就好了):

1. 汇兑-单笔对公(CFRT02)交易

2. 查询帐户明细(新全量)(CQRC09)交易

3. 查询金融交易处理状态(CQRT71)交易

4. 本行汇总批量代发(实时)(IBAF04)

5. 他行汇总批量代发(IBBF23)交易

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


农行对接交易大致流程如下图,企业财务系统ERP(可以理解为客户端或者我们的管理系统)发起交易请求至农行企业通讯平台ICT(这个相当于一个中转站,是一个软件,部署在服务器上面的),通讯平台ICT就会发送请求至农行管理服务端ICS上面,最后就是处理交易。这次对接主要就是弄ERP(客户端)到ICT这部分


前期准备 


农行企业通讯平台ICT(中转站)如下图,相信要对接开发的朋友肯定会有,打开登录后,会看到一个监听端口,默认是15999,不是必须要这个东西在你电脑上,服务器上面有也行,只要知道对方的ip地址以及端口号就可以了


SocketClient工具类,用来进行socket请求用的,至于什么是socket请求,自己百度了,下面是我写的请求方法

public class SocketClient {

    private final Logger log = LoggerFactory.getLogger(getClass());

    // ip
    private String host;

    // 端口
    private Integer port;

    /**
     * TCP Socket
     * */
    public Map<String, Object> tcp(String message, String charsetName) {
        /**
         * 返回结果:<key, Value>
         *  data: 应答数据
         *  errorMessage:程序错误信息
         *  isAlreadyResq: 是否已经发送请求
         * */
        Map<String, Object> resultMap = new HashMap<>();
        if (StringUtils.isBlank(this.host)) {
            try {
                this.host = this.getLocalIp();
            } catch (UnknownHostException e) {
                log.error("获取本机ip失败", e);
            }
            if (StringUtils.isBlank(this.host)) {
                resultMap.put("errorMessage", "获取服务器IP地址失败");
                return resultMap;
            }
        }

        try {
            // 与服务端建立连接
            Socket socket = new Socket(this.host, this.port);
            // 60s超时
            socket.setSoTimeout(60000);
            // 建立连接后获得输出流,发送数据
            socket.getOutputStream().write(message.getBytes(charsetName));
            // 通过shutdownOutput 告诉接收端已经发送完数据,客户端后续只能接受数据
            socket.shutdownOutput();
            resultMap.put("isAlreadyResq", true);
            // 获得响应数据流
            InputStream socketInputStream = socket.getInputStream();
            // 接收服务端返回来的数据
            byte[] bytes = null;
            //读取服务器返回的消息
            if (socketInputStream != null && !socketInputStream.markSupported()) {
                bytes = IOUtils.toByteArray(socketInputStream);
                resultMap.put("errorMessage", null);
                resultMap.put("data", bytes);
            } else {
                resultMap.put("errorMessage", "农行直联无响应数据");
            }
            // 关闭输入流
            socketInputStream.close();
            // 关闭socket
            socket.close();
        } catch (ConnectException e) {
            String error = new StringBuffer("socket连接失败,ip:").append(this.host).append(",端口:").append(this.port).toString();
            resultMap.put("errorMessage", "socket连接失败,请检查农行银企通平台是否已开启");
            log.error(error);
        } catch (SocketTimeoutException e) {
            resultMap.put("isAlreadyResq", true);
            resultMap.put("errorMessage", "socket请求超时");
            log.error("socket请求超时,请求数据为:" + message);
        } catch (IOException e) {
            resultMap.put("isAlreadyResq", true);
            resultMap.put("errorMessage", "socket请求异常");
            log.error("socket请求异常", e);
        }
        return resultMap;
    }

    /**
     * 获取本机Ip
     * */
    private static String getLocalIp() throws UnknownHostException {
        InetAddress address = null;  //获取本机IP地址
        address = InetAddress.getLocalHost();
        String localIP = address.getHostAddress();  //获取本机IP地址字符串
        return localIP;
    }

    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }

    public Integer getPort() {
        return port;
    }

    public void setPort(Integer port) {
        this.port = port;
    }

    public SocketClient(String host, Integer port) {
        this.host = host;
        this.port = port;
    }

    public SocketClient(Integer port) {
        this.port = port;
    }

    public SocketClient() {}
}

XmlUtil工具类,用来把对象转成xml格式字符串,或xml格式字符串转成对象

public class XmlUtil {
    /**
     * 将String类型的xml转换成对象
     */
     public static Object convertXmlStrToObject(Class<?> clazz, String xmlStr) throws JAXBException {
         JAXBContext context = JAXBContext.newInstance(clazz);
         // 进行将Xml转成对象的核心接口
         Unmarshaller unmarshal = context.createUnmarshaller();
         StringReader sr = new StringReader(xmlStr);
         Object xmlObject = unmarshal.unmarshal(sr);
         return xmlObject;
     }

    /**
     * Object 转 xml字符串
     * isFormat:是否需要格式化xml内容
     * isDelHead:是否删除头部的 < ?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
     * */
    public static String objectToXml(Object obj, boolean isFormat, boolean isDelHead) throws JAXBException {
        JAXBContext context = JAXBContext.newInstance(obj.getClass());
        Marshaller marshaller = context.createMarshaller();
        if (isFormat) {
            // 格式化内容
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        }
        StringWriter writer = new StringWriter();
        marshaller.marshal(obj, writer);
        String xmlResult = writer.toString();
        if (isDelHead) {
            // 去掉开头的<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
            return xmlResult.replaceAll("\\<\\?xml.*\\?>", "");
        }
        return xmlResult;
    }
}

 说说农行的接口对接,其实是通过xml报文形式交互,说白点就是,我发一串xml格式的字符串(请求报文)过去,农行响应返回一串xml字符串(应答报文)回来,然后我们解析返回来的xml字符串,大致就是这个意思。

下面是其中一个请求报文,查账户余额的

而每个接口请求的报文,和返回来的报文,有一部分是相同的,有些是不同的,就是有公共字段,这些字段可以在开发文档里面看得到,我放出来一下


接下来说说开发对接思路,因为请求和响应都是一串xml字符串,为了方便开发,我就把他们定义成了一个实体对象,方便在java代码中操作,我分成了请求报文对象和应答报文对象,因为有公共字段,我又定义了两个基类存放报文公共字段,让后面的实体对象继承。

请求报文公共对象

/**
 * ERP TO ICT 请求报文公共字段
 * */
@XmlRootElement(name = "ap")
@XmlAccessorType(XmlAccessType.FIELD)
public class RequestBaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;

    /**
     * 交易代码
     * */
    @XmlElement(name = "CCTransCode")
    private String cctransCode;

    /**
     * 产品标识 固定为ICC
     * */
    @XmlElement(name = "ProductID")
    private String productID;

    /**
     * 渠道标识 固定为ERP
     * */
    @XmlElement(name = "ChannelType")
    private String channelType;

    /**
     * 企业技监局代码/客户号
     * */
    @XmlElement(name = "CorpNo")
    private String corpNo;

    /**
     * 企业操作员编号
     * */
    @XmlElement(name = "OpNo")
    private String opNo;

    /**
     * 认证码
     * */
    @XmlElement(name = "AuthNo")
    private String authNo;

    /**
     * 请求渠道流水号
     * */
    @XmlElement(name = "ReqSeqNo")
    private String reqSeqNo;

    /**
     * 请求日期 yyyyMMdd
     * */
    @XmlElement(name = "ReqDate")
    private String reqDate;

    /**
     * 请求时间 HHmmss
     * */
    @XmlElement(name = "ReqTime")
    private String reqTime;

    /**
     * 数字签名
     * */
    @XmlElement(name = "Sign")
    private String sign;

    /**
     * 查询时是否加密 0 不加密 1 加密 请求接口时设置
     * */
    @XmlTransient
    private String isEncryption;
}

应答报文公共对象

/**
 * ICT TO ERP 应答报文公共字段
 * */
@XmlAccessorType(XmlAccessType.FIELD)
public class ResponseBaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;

    /**
     * 交易代码
     * */
    @XmlElement(name = "CCTransCode")
    private String cCTransCode;

    /**
     * 返回来源
     * */
    @XmlElement(name = "RespSource")
    private String respSource;

    /**
     * 应答流水号
     * */
    @XmlElement(name = "RespSeqNo")
    private String respSeqNo;

    /**
     * 返回日期
     * */
    @XmlElement(name = "RespDate")
    private String respDate;

    /**
     * 返回时间
     * */
    @XmlElement(name = "RespTime")
    private String respTime;

    /**
     * 返回码
     * */
    @XmlElement(name = "RespCode")
    private String respCode;

    /**
     * 返回信息
     * */
    @XmlElement(name = "RespInfo")
    private String respInfo;

    /**
     * 返回扩展信息
     * */
    @XmlElement(name = "RxtInfo")
    private String rxtInfo;

    /**
     * 文件标识 0-无文件 1-有文件
     * */
    @XmlElement(name = "FileFlag")
    private Integer fileFlag;

    /**
     * ???
     * */
    @XmlElement(name = "Cme")
    private Cme cme;

    /**
     * ???
     * */
    @XmlElement(name = "Cmp")
    private Cmp cmp;

    @XmlAccessorType(XmlAccessType.FIELD)
    public static class Cme {
        /**
         * 记录数
         * */
        @XmlElement(name = "RecordNum")
        private Integer recordNum;

        /**
         * 字段名 |分隔
         * */
        @XmlElement(name = "RespHead")
        private String respHead;

        /**
         * 字段数
         * */
        @XmlElement(name = "FieldNum")
        private Integer fieldNum;
    }

    @XmlAccessorType(XmlAccessType.FIELD)
    public static class Cmp {
        /**
         * 私有数据区
         * */
        @XmlElement(name = "RespPrvData")
        private String respPrvData;

        /**
         * 文件名
         * */
        @XmlElement(name = "BatchFileName")
        private String batchFileName;

        /**
         * 续查标志
         * */
        @XmlElement(name = "ContFlag")
        private String contFlag;

        /**
         * 流水状态 具体见TransStaEnum
         * */
        @XmlElement(name = "TransSta")
        private String transSta;
    }
}

公司没有用Lombok,我把getset方法删掉了,自己加吧。至于字段上面那些注解,就是用来将对象跟xml进行转换用的。

@XmlRootElement(name = "ap")  定义xml根节点为ap

@XmlAccessorType(XmlAccessType.FIELD) 把所有字段都绑定上xml

@XmlElement(name = "CCTransCode") 定义这个字段对应的xml标签名

@XmlTransient 转换时忽略此字段

由于接口有点多,对应实体对象就不一一放出来了,放一个出来就可以了


总之实体对象这方面,我的做法就是一个接口对应两个实体对象(一个请求,一个响应),具体请求接口实体对象 继承 请求实体基类,响应的实体对象 继承 响应基类,下面是我的代码结构

前期准备结束,具体开发看下期文章。

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

望长城内外,惟余莽莽;大河上下,顿失滔滔

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐