农行银企直联
前段时间项目中接入了农行的银企直联来完成代发的功能,当我拿到银行方面给过来的文档和资料后,发现和招行的银企直联模式差不多,大概就是:在window机器上开一个类似于前置机的小程序,作为我们和银行服务器直联数据连接的中介,我们发送xml数据给前置机,前置机再将数据加密后发送给银行服务器。但是万万没想到农行这个银企直联给我搞了不小的麻烦,他们的文档写的简直是不忍直视,接口返回码也模糊不清,没有明确说明。现在我把踩过的坑给分享一下。
- 准备开始
在开始之前我们会拿到2个东西,一个是中国农业银行银企通平台(4.70版).msi安装包,另一个是现金管理银企直连接入开发手册V1.2.1.wps接口文档说明
开始安装前置机程序,完成之后是这个样子:
测试用的客户号、操作员代码、操作员密码都会一并提供过来。
注意:正式环境下是需要插入一个key宝,由于现在是测试环境,在安装目录的etc路径下,用记事本编辑 etc\LoginSet.xml,将 IsKey 节点中的内容改成0,就可以不用KEY登录了。 系统设置
点击系统设置,在里面配置我们要用的模式:ERP公网接入、本地服务器地址、通讯协议、监听的端口等等。
直接上几张图吧:
注意:这里有一个坑需要说明一下:农行的这个程序是不支持http 协议(虽然他上面写着可以选择,无语)所以我们要用tcp协议。组装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的输出流里设置了字符的编码,经过血一般经历后终于想到了这里,然后彻底解决中文乱码的问题。
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);
测试结果:
补充代码
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; }
代码下载地址
https://download.csdn.net/download/wei389083222/10498109
结语:反手支持一下吧