农行银企直联Java

农行银企直联

前段时间项目中接入了农行的银企直联来完成代发的功能,当我拿到银行方面给过来的文档和资料后,发现和招行的银企直联模式差不多,大概就是:在window机器上开一个类似于前置机的小程序,作为我们和银行服务器直联数据连接的中介,我们发送xml数据给前置机,前置机再将数据加密后发送给银行服务器。但是万万没想到农行这个银企直联给我搞了不小的麻烦,他们的文档写的简直是不忍直视,接口返回码也模糊不清,没有明确说明。现在我把踩过的坑给分享一下。

  1. 准备开始
    在开始之前我们会拿到2个东西,一个是中国农业银行银企通平台(4.70版).msi安装包,另一个是现金管理银企直连接入开发手册V1.2.1.wps接口文档说明
    开始安装前置机程序,完成之后是这个样子:
    这里写图片描述
    测试用的客户号、操作员代码、操作员密码都会一并提供过来。
    注意:正式环境下是需要插入一个key宝,由于现在是测试环境,在安装目录的etc路径下,用记事本编辑 etc\LoginSet.xml,将 IsKey 节点中的内容改成0,就可以不用KEY登录了。
  2. 系统设置
    点击系统设置,在里面配置我们要用的模式:ERP公网接入、本地服务器地址、通讯协议、监听的端口等等。
    直接上几张图吧:
    这里写图片描述
    这里写图片描述
    这里写图片描述
    这里写图片描述
    这里写图片描述
    注意:这里有一个坑需要说明一下:农行的这个程序是不支持http 协议(虽然他上面写着可以选择,无语)所以我们要用tcp协议。

  3. 组装XML数据
    看到上图,已经成功的监听到了15999端口,现在我们就要向前置机所在的机器的ip+port的这个URL上推送XML数据,例如:192.168.1.111:15999
    这里写图片描述
    这里举一个范例:汇兑-单笔对似,xml数据报文是这样要求的。
    这里写图片描述
    java代码实例:

    //5.汇兑-单笔对私
        ApRoot root = new ApRoot();
        root.setCCTransCode("CFRT21");
        root.setAmt("9.10");
        builder.setRoot(root);
        ApCmp cmp = new ApCmp();
        cmp.setDbAccNo("361101040010679");
        cmp.setDbProv("05");
        cmp.setDbCur("01");
        cmp.setDbLogAccNo("");
        cmp.setCrAccNo("6228453296002816764");
        cmp.setCrProv("");
        cmp.setCrCur("01");
        cmp.setCrLogAccNo("");
        cmp.setConFlag("1");
        builder.setCmp(cmp);
        ApCorp corp = new ApCorp();
        corp.setPsFlag("");
        corp.setBookingFlag("0");
        corp.setBookingDate("");
        corp.setBookingTime("");
        corp.setUrgencyFlag("0");
        corp.setOthBankFlag("0");
        corp.setCrAccName("郑春广");
        corp.setDbAccName("内猛关仪太彩悟佑慊古丝");
        corp.setWhyUse("测试");
        corp.setPostscript("测试");
        builder.setCorp(corp);
    安装要求组装数据:
    
    String s = builder.toXmlString(builder);
    /**
     * 请求数据:加密标识(1加密,0不加密) + 请求xml数据的长度(默认7位,不够补空格) + 请求的xml
     * @param s 请求的xml
     * @author jieYW
     * @date 2018/5/29
     * @return java.lang.String
     */
    public String genRequestData(String s)throws Exception{
        return "1" + String.format("%1$-6s", s.getBytes("gbk").length) + s;
    }

    注意:这里也有一个大坑,字符编码必须通过gbk的编码。前置机内部是通过gbk来解码的。这里如果不设置gbk,你在报文中有汉字的时候就gg了,会一直报这个错误:接收请求报文失败 -接收报体失败 - POLL失败退出 - 偏移量 = 750,当时这个问题困扰了我一段时间,因为我是知道他字符编码是gbk的,所以我只在socket的输出流里设置了字符的编码,经过血一般经历后终于想到了这里,然后彻底解决中文乱码的问题。

  4. socket发送数据
    将上述组装好的数据通过tcp协议发送给前置机:

    String s = builder.toXmlString(builder);
    ApHttpRequest request = new ApHttpRequest();
    String s1 = request.socketSendAndReceive("192.168.1.111", 15999, builder.genRequestData(s));
    System.out.println("接受数据:" + s1);

    测试结果:
    这里写图片描述

  5. 补充代码
    XML组装:

    /**
     * 将ApXmlBuilder格式的数据转化为xml形式
     *
     * @param builder
     * @author jieYW
     * @date 2018/5/29
     * @return java.lang.String
     */
    public String toXmlString(ApXmlBuilder builder)throws Exception{
        StringBuffer sb = new StringBuffer("<ap>");
        Field[] fields = builder.getClass().getDeclaredFields();
        for (int i = 0; i < fields.length; i++) {
            Field field = fields[i];
            field.setAccessible(true);
            Object item = field.get(builder);
            if(item == null){
                continue;
            }
            String name = DaoSupport.toFirstUpperCase(field.getName());
            if(!name.equals("Root")){
                sb.append("<" + name + ">");
            }
            Field[] itemFields = item.getClass().getDeclaredFields();
            for (int j = 0; j < itemFields.length; j++) {
                Field itemField = itemFields[j];
                itemField.setAccessible(true);
                Object itemObject = itemField.get(item);
                if(itemObject == null){
                    continue;
                }
                String itemFieldName = itemField.getName();
                sb.append("<" + itemFieldName + ">");
                sb.append(itemField.get(item).toString());
                sb.append("</" + itemFieldName + ">");
            }
            if(!name.equals("Root")){
                sb.append("</" + name + ">");
            }
        }
        sb.append("</ap>");
        return sb.toString();
    }

    发送报文:

     public String socketSendAndReceive(String url, int port,String data)throws Exception{
        System.out.println("请求数据:" + data);
        Socket socket = new Socket(url, port);
        OutputStream bw = socket.getOutputStream();
        bw.write(data.getBytes("gbk"));
        bw.flush();
        InputStream ips = socket.getInputStream();
        StringBuffer sb = new StringBuffer();
        int len = 0;
        byte[] buf = new byte[1024];
        while((len=ips.read(buf))!=-1){
            sb.append(new String(buf,0,len,"gbk"));
        }
        bw.close();
        ips.close();
        socket.close();
        return sb.toString();
    }

    解析XML:

    /**
     * 将xml数据解析为ApXmlBuilde格式的数据
     *
     * @param msg
     * @author jieYW
     * @date 2018/5/29
     * @return com.mind.pay.abc.ap.ApXmlBuilder
     */
    public static ApXmlBuilder parseXml(String msg)throws Exception {
        ApXmlBuilder builder = new ApXmlBuilder();
        Method[] methods  = builder.getClass().getMethods();
        ApRoot apRoot = new ApRoot();
        Method[] rootMethods = apRoot.getClass().getMethods();
    
        Map<String,Method> methodMap = new HashMap<>();
        for (Method method : methods) {
            methodMap.put(method.getName(),method);
        }
        InputStream inputStream = new ByteArrayInputStream(msg.getBytes("UTF-8"));
        SAXReader reader = new SAXReader();
        Document document = reader.read(inputStream);
        Element root = document.getRootElement();
        List<Element> elementList = root.elements();
        // 遍历所有子节点
        for (Element e : elementList){
            if(methodMap.keySet().contains("set" + e.getName())){
                Class<?> aClass = Class.forName("com.mind.pay.abc.ap.Ap" + e.getName());
                Object itemObject = aClass.newInstance();
                List<Element>  items = e.elements();
                for (Element itemElement : items) {
                    Method[] itemMethods = itemObject.getClass().getMethods();
                    for (Method itemMethod : itemMethods) {
                        //如果字段存在,invoke方法赋值
                        if(itemMethod.getName().contains("set"+itemElement.getName())){
                            itemMethod.invoke(itemObject,itemElement.getText());
                        }
                    }
                }
                methodMap.get("set" + e.getName()).invoke(builder,itemObject);
            }else{
                //根目录下的参数,封装到apRoot中
                for (Method rootMethod : rootMethods) {
                    //如果字段存在,invoke方法赋值
                    if(rootMethod.getName().contains("set"+e.getName())){
                        rootMethod.invoke(apRoot,e.getText());
                    }
                }
                builder.setRoot(apRoot);
            }
         }
        // 释放资源
        inputStream.close();
        inputStream = null;
        return builder;
    }
  6. 代码下载地址
    https://download.csdn.net/download/wei389083222/10498109


结语:反手支持一下吧

1 数据字典定义 6 1.1 字典键值索引(0) 6 1.2 不得转让标识(200) 6 1.3 签收标记(201) 6 1.4 类别(202) 6 1.5 票据类别(203) 7 1.6 追索类型(204) 7 1.7 (再) 贴现种类(205) 7 1.8 到期无条件支付承诺(206) 7 1.9 线上清算标记(207) 7 1.10 申请回复类型(208) 7 1.11 业务类型(209) 7 1.12 回复类型(210) 8 1.13 撤销类型(211) 8 1.14 提示类型(212) 8 1.15 结清状态(213) 9 1.16 业务通知查询类型(214) 9 1.17 处理标识(215) 9 1.18 逾期标识(216) 9 1.19 到期付款标识(217) 9 1.20 追索理由代码(218) 9 1.21 贴现入账行行号(219) 9 1.22 交易代码(电票交易代码,业务结果查询时输入) 10 2 交易接口 11 2.1 通用查询交易 11 2.1.1 查询客户签约信息(CQRT05) 11 2.1.2 查询行名(CQRT06) 12 2.1.3 通用申请类首页查询(CQRM02) 13 2.1.4 查询可回复票据(CQRM01) 16 2.1.5 查询票据信息(多笔)(CQRM44) 19 2.1.6 查询票据详细信息(CQRT14) 21 2.1.7 查询业务种类(CQRM04) 24 2.1.8 查询票据流转信息(CQRM06) 27 2.1.9 业务结果查询(通过账号)(CMLR34) 29 2.2 出票 31 2.2.1 出票申请(CFRT51) 31 2.2.2 电票出票结果查询(CQRM42) 34 2.3 提示收票 36 2.3.1 查询可提示收票票据(CQRT08) 36 2.3.2 提示收票申请(CFRT53) 41 2.4 背书转让 44 2.4.1 查询可背书转让票据(CQRT10) 44 2.4.2 背书转让申请(CFRT55) 46 2.5 承兑回复 48 2.5.1 查询可承兑回复票据(CQRT07) 48 2.5.2 承兑回复申请(CFRT52) 53 2.6 收票回复/背书转让回复 54 2.6.1 查询可收票回复票据(CQRT09) 54 2.6.2 查询可背书回复票据(CQRT11) 58 2.6.3 收票回复申请(CFRT54)/背书回复申请(CFRT71) 62 2.7 质押回复 64 2.7.1 查询可质押回复票据(CQRT18) 64 2.7.2 质押回复申请(CFRT61) 67 2.8 质押解除回复 69 2.8.1 查询可质押解除回复票据(CQRT20) 69 2.8.2 质押解除回复申请(CFRT63) 73 2.9 保证回复 74 2.9.1 保证回复详细查询(CQRT21) 74 2.9.2 保证回复申请(CFRT65) 78 2.10 同意清偿回复 80 2.10.1 查询可同意清偿回复票据(CQRT24) 80 2.10.2 同意清偿回复申请(CFRT68) 83 2.11 提示付款 85 2.11.1 提示付款明细查询(CQRT13) 85 2.11.2 提示付款申请(CFRT58) 87 2.12 质押 89 2.12.1 质押申请(CFRT60) 89 2.13 解质押申请 91 2.13.1 质押解除明细查询(CQRT19) 91 2.13.2 质押解除申请(CFRT62) 93 2.14 保证申请 96 2.14.1 保证申请(CFRT64) 96 2.15 追索申请 98 2.15.1 追索通知明细查询(CQRM10) 98 2.15.2 根据ID查被追索人(CQRT22) 101 2.15.3 追索通知申请(CFRT66) 103 2.16 贴现 105 2.16.1 贴现实付金额试算报文(CQRT12) 105 2.16.2 贴现申请报文(CFRT56) 106 2.17 撤销 110 2.17.1 查询可撤销票据(CQRM03) 110 2.17.2 撤销(CFRT57) 112 2.18 撤票 114 2.18.1 撤票明细查询(CQRT25) 114 2.18.2 撤票明细申请(CFRT69) 118 2.19 同意清偿申请 121 2.19.1 追索同意清偿明细查询(CQRT23) 121 2.19.2 追索同意清偿申请(CFRT67) 124 2.20 支付信用信息查询 127 2.20.1 查询信用记录(CQRT15) 127 2.20.2 查询申请提交(CQRT16) 128 2.20.3 查询申请(CQRT17) 129
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值