java农业银行-企业银行ERP接口开发(2-接口对接)(汇兑-单笔对公、对私)

本文主要是介绍java农业银行-企业银行ERP接口开发(2-接口对接)(汇兑-单笔对公、对私),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

上一篇文章:java农业银行-企业银行ERP接口开发(1-前期准备)icon-default.png?t=N7T8https://blog.csdn.net/new_public/article/details/133882741

 这篇文章我们主要讲具体的接口对接


我新建了一个工具类,专门用来对接农行接口,并且加上了spring的@Component 注解,把这个类交给spring管理,因为农行直联需要一些配置,比如农行通讯平台ICT的ip地址以及端口,本地开发的时候可以直接写死,但发布上线的时候,这些东西就要放到项目配置文件里面了,总之就是为了一些配置信息方便配置和取值,所以交给spring管理。

建了一个AbcErpToIctSocket工具类,并引入农行通讯平台ICT的IP地址以及端口号,用来对接农行接口


 我以其中一个接口:CFRT02(汇兑-单笔对公,对私)为例


CFRT02接口请求报文格式(这里只展示了此接口特有的字段,请求时,是公共请求字段+接口特有字段)

 CFRT02接口应答报文格式(这里只展示了此接口特有的字段,接口返回时,是公共响应字段+接口特有字段)


根据CFRT02请求报文特有的字段,建一个实体类CFRT02RequestDTO(get set方法太多了,先删掉了,后面自己加),并继承请求基类(请求基类在上篇文章讲了,用于接口请求使用。

package com.sysfunc.express.fin.dto.erp.request;import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;/*** CFRT02(汇兑-单笔对公)请求报文字段* */
@XmlRootElement(name = "ap")
public class CFRT02RequestDTO extends RequestBaseEntity {/*** 金额* */@XmlElement(name = "Amt")private Double amt;/*** ???* */@XmlElement(name = "Cmp")private Cmp cmp;/*** ???* */@XmlElement(name = "Corp")private Corp corp;@XmlAccessorType(XmlAccessType.FIELD)public static class Corp {/*** 预约日期 yyyyMMdd* */@XmlElement(name = "BookingDate")private String bookingDate;/*** 预约时间 HHmmss* */@XmlElement(name = "BookingTime")private String bookingTime;/*** 预约标志* */@XmlElement(name = "BookingFlag")private String bookingFlag;/*** 附言  跨行汇兑时,附言只支持char(60)长* */@XmlElement(name = "Postscript")private String postscript;/*** 他行标志* */@XmlElement(name = "OthBankFlag")private String othBankFlag;/*** 贷方户名* */@XmlElement(name = "CrAccName")private String crAccName;/*** 贷方开户行行名* */@XmlElement(name = "CrBankName")private String crBankName;/*** 贷方行号* */@XmlElement(name = "CrBankNo")private String crBankNo;/*** 借方户名* */@XmlElement(name = "DbAccName")private String dbAccName;/*** 用途* */@XmlElement(name = "WhyUse")private String whyUse;}@XmlAccessorType(XmlAccessType.FIELD)public static class Cmp {/*** 借方省市代码* */@XmlElement(name = "DbProv")private String dbProv;/*** 借方账号* */@XmlElement(name = "DbAccNo")private String dbAccNo;/*** 借方货币号* */@XmlElement(name = "DbCur")private String dbCur;/*** 借方多级账簿* */@XmlElement(name = "DbLogAccNo")private String dbLogAccNo;/*** 贷方账号* */@XmlElement(name = "CrAccNo")private String crAccNo;/*** 贷方省市代码* */@XmlElement(name = "CrProv")private String crProv;/*** 贷方货币号* */@XmlElement(name = "CrCur")private String crCur;/*** 贷方多级账簿* */@XmlElement(name = "CrLogAccNo")private String crLogAccNo;/*** 贷方户名校验标志 1 是 0 否* */@XmlElement(name = "ConFlag")private String conFlag;}
}

根据CFRT02响应报文特有的字段,建一个实体类CFRT02ResponseDTO,并继承响应基类(响应基类在上篇文章讲了,用于接收接口响应报文。 

package com.sysfunc.express.fin.dto.erp.response;import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;/*** CFRT02(汇兑-单笔对公) 应答报文字段* */
@XmlRootElement(name = "ap")
@XmlAccessorType(XmlAccessType.FIELD)
public class CFRT02ResponseDTO extends ResponseBaseEntity {/*** ???* */@XmlElement(name = "Corp")private Corp corp;@XmlAccessorType(XmlAccessType.FIELD)public static class Corp {/*** 落地处理标志 0 不落地 1 落地* */@XmlElement(name = "WaitFlag")private String waitFlag;public String getWaitFlag() {return waitFlag;}public void setWaitFlag(String waitFlag) {this.waitFlag = waitFlag;}}public Corp getCorp() {return corp;}public void setCorp(Corp corp) {this.corp = corp;}
}

接下来在AbcErpToIctSocket工具类建一个方法,用来请求CFRT02接口

    /*** CFRT02 汇兑-单笔对公,对私* */public  Map<String, Object> cfrt02(CFRT02RequestDTO requestEntity) {if (requestEntity == null) {log.error("CFRT02报文请求对象不能为空!");return null;}// 设置 交易代码 为CFRT02,标识本次是请求CFRT02接口requestEntity.setCctransCode(CctransCodeEnum.CFRT02.code());// 请求是否加密,这个参数后面讲requestEntity.setIsEncryption("0");// 请求流水号,本次请求的唯一标识,用于后续查询相关业务,比如 查询交易状态,非常重要if (StringUtils.isBlank(requestEntity.getReqSeqNo())) {requestEntity.setReqSeqNo(UUID.randomUUID().toString().replaceAll("-", ""));}// 开始socket请求Map<String, Object> socketResponseMap = socket(requestEntity, CFRT02ResponseDTO.class);return socketResponseMap;}

上面方法用了一个socket具体请求方法,这个是当前类定义的,所有接口的具体socket请求都调用这个方法。

    /*** 接口socket请求调用* @requestEntity 查询报文* @responseClass 应答报文class* */private  <T extends RequestBaseEntity, R extends ResponseBaseEntity> Map<String, Object> socket(T requestEntity, Class<R> responseClass) {/*** 返回结果:*  requestMessage:请求报文*  responseMessage:应答报文*  responseEntity:应答报文对象*  errorMessage: 错误信息*  isAlreadyResq: 是否已发送请求报文* */Map<String, Object> resultMap = new HashMap<>();// 设置报文基本信息requestBeforeSetCmeBase(requestEntity);// 请求对象转成xml报文String requestMessage = requestObjectToXml(requestEntity, requestEntity.getIsEncryption());if (StringUtils.isBlank(requestMessage)) {resultMap.put("errorMessage", "生成socket请求报文出错");return resultMap;}// 记录请求报文resultMap.put("requestMessage", requestMessage);// 日志唯一标识String uuid = UUID.randomUUID().toString().replaceAll("-", "").toUpperCase();log.debug(new StringBuffer(uuid).append(" --> 农行").append(requestEntity.getCctransCode()).append("接口请求,请求报文内容如下:").toString());log.debug(requestMessage);// 开始socket请求SocketClient socketClient = new SocketClient(socketIpAddress, socketPort);Map<String, Object> tcpMap = socketClient.tcp(requestMessage, "GBK");// 记录是否已发送请求报文resultMap.put("isAlreadyResq", tcpMap.get("isAlreadyResq"));// 如果有错误信息if (tcpMap.get("errorMessage") != null) {resultMap.put("errorMessage", tcpMap.get("errorMessage"));return resultMap;}byte[] responseBytes = (byte[]) tcpMap.get("data");log.debug(new StringBuffer(uuid).append(" --> 农行").append(requestEntity.getCctransCode()).append("接口请求,应答报文内容如下:").toString());// 应答报文对象R responseEntity = null;try {// 获得应答报文字符串String responseMessageStr = new String(responseBytes, "GBK");log.debug(responseMessageStr);if (StringUtils.isNotBlank(responseMessageStr)) {resultMap.put("responseMessage", responseMessageStr.replaceAll("\n|\\s", ""));}// bytes转成应答报文对象responseEntity = responseBytesToObject(responseMessageStr, responseClass, uuid);// 记录应答报文对象resultMap.put("responseEntity", responseEntity);} catch (UnsupportedEncodingException e) {log.error("应答报文Bytes 转 字符串失败...");}return resultMap;}

上面代码步骤大致是这样的:

 1 :先设置请求基本信息(requestBeforeSetCmeBase(requestEntity);),就是那些请求公共字段

    /*** 请求前 -> 设置报文基本信息* */private  <T extends RequestBaseEntity> T requestBeforeSetCmeBase(T requestEntity) {requestEntity.setProductID("ICC");requestEntity.setChannelType("ERP");requestEntity.setOpNo("");requestEntity.setCorpNo("");requestEntity.setAuthNo("");if (requestEntity.getReqSeqNo() == null) {requestEntity.setReqSeqNo("");}requestEntity.setReqDate(DateFormatUtil.format("yyyyMMdd", new Date()));requestEntity.setReqTime(DateFormatUtil.format("HHmmss", new Date()));if (requestEntity.getSign() == null) {requestEntity.setSign("");}return null;}

 2 :请求对象转成xml报文

String requestMessage = requestObjectToXml(requestEntity, requestEntity.getIsEncryption());

 农行请求报文有个要求,就是xml报文前面加上几个数字(包头)

意思就是xml报文前面加上7位数字,1位加密位,6位报文字节长度,不足7位后面补0,而报文的长度是按GBK编码进行计算的,一个中文算两个字节长度。

这里说一下,请求报文的各个标签之间不要默认加空格之类的,不要把xml报文拿到Xml格式化工具里面格式化后后直接用。

xml报文应该是这样:0170   <ap><CCTransCode>CFRT02</CCTransCode>...其他内容</ap>

下面代码,XmlUtil.objectToXml 是上一篇文章定义的xml工具类方法。 

    /*** 请求报文对象转成xml字符串,前头加包头* @object 请求对象* @isEncryption 是否加密 0 不加密 1加密* */private  String requestObjectToXml(Object object, String isEncryption) {try {// 对象转成xml字符串String xmlMessage = XmlUtil.objectToXml(object, false, true);// xml报文之前加上包头, 包头第一位为是否为加密包标志,再加上6个字节的字符表示数据包的长度,如果长度不足6位则右边用空格补足String message = new StringBuffer(isEncryption).append(String.format("%-6s", xmlMessage.getBytes("GBK").length)).append(xmlMessage).toString();return message;} catch (JAXBException | UnsupportedEncodingException e) {log.error("请求报文对象转xml报文出错", e);return null;}}

 3 :获取xml请求报文后,创建一个socket请求,SocketClient工具类上篇文章说了

// socketIpAddress,socketPort 农行通讯平台ICT的IP地址以及端口号,前面配置引入了
SocketClient socketClient = new SocketClient(socketIpAddress, socketPort);
Map<String, Object> tcpMap = socketClient.tcp(requestMessage, "GBK");

 4 :将socket请求的返回值解析,获得应答报文等信息

String responseMessageStr = new String(responseBytes, "GBK");

 5 :将应答报文转成应答报文对象

responseEntity = responseBytesToObject(responseMessageStr, responseClass, uuid);
    /*** 应答报文bytes转实体对象* @bytes 报文bytes* @classz 返回对象class* @uuid 日志标记id* */private   <T extends ResponseBaseEntity> T responseBytesToObject(String responseMessageStr, Class<T> classz, String uuid) {if (StringUtils.isNotBlank(responseMessageStr)) {try {// 去掉前面的包头responseMessageStr = responseMessageStr.substring(responseMessageStr.indexOf("<ap>"), responseMessageStr.lastIndexOf("</ap>") + 5);// 报文结果xml转成实体对象return (T) XmlUtil.convertXmlStrToObject(classz, responseMessageStr);} catch (JAXBException e) {log.error(new StringBuffer(uuid).append(" --> 报文xml结果转实体对象失败...").toString(), e);}}return null;}

接口对接到这里就可以,接下来测试一下


先创建CFRT02RequestDTO请求报文对象

CFRT02RequestDTO cfrt02RequestDTO = new CFRT02RequestDTO();
// 本次请求流水号,唯一标识,很重要,后期查询交易状态使用
cfrt02RequestDTO.setReqSeqNo();
// 交易金额
cfrt02RequestDTO.setAmt();
CFRT02RequestDTO.Cmp cmp = new CFRT02RequestDTO.Cmp();
// 借方(扣钱的那一方)省份代码,我定义了一个枚举类,广东省的是44
cmp.setDbProv(ProvinceCodeEnum.Guangdong.code());
// 借方货币代码,人民币是01
cmp.setDbCur(CurrencyEnum.CNY.code());
// 借方账号
cmp.setDbAccNo();
// 贷方(收款的那一方)账号
cmp.setCrAccNo();
// 贷方货币代码
cmp.setCrCur(CurrencyEnum.CNY.code());
// 是否校验贷方户名是否正确
cmp.setConFlag("1");
cfrt02RequestDTO.setCmp(cmp);CFRT02RequestDTO.Corp corp = new CFRT02RequestDTO.Corp();
// 贷方户名
corp.setCrAccName();
// 贷方开户行
corp.setCrBankName();
// 贷方支行号,可以百度查
corp.setCrBankNo();
// 借方户名
corp.setDbAccName();
// 贷方是否农行,0 农行, 1 他行
corp.setOthBankFlag();
// 设置交易附言
if (附言.length() > 30) {// 附言只支持60个字节String feeDesc = 附言.substring(0, 30);corp.setPostscript(feeDesc);
}
cfrt02RequestDTO.setCorp(corp);

注入农行接口请求AbcErpToIctSocket 工具类

@Autowired
private AbcErpToIctSocket abcErpToIctSocket;

开始请求,如果有多笔交易,从客户体验角度来看,最好开启一个或多个线程处理,不然会等很久才请求完。

// 开线程 请求农行接口
new Thread(() -> {// socket 请求Map<String, Object> erpSocketResultMap = abcErpToIctSocket.cfrt02(cfrt02SocketMessageMap.get(payBatchNo));// 处理请求结果this.handlerSocketResponse(erpSocketResultMap);
}).start();

请求完成后,解析返回值,下面代码自己补充对应业务逻辑

    /*** 处理socket应答* @erpSocketResultMap socket返回结果* */private void handlerSocketResponse(Map<String, Object> erpSocketResultMap) {// 记录请求报文if (erpSocketResultMap.get("requestMessage") != null) {erpSocketResultMap.get("requestMessage").toString();}// 记录应答报文if (erpSocketResultMap.get("responseMessage") != null) {erpSocketResultMap.get("responseMessage").toString();}// 如果已经发送socket请求if (erpSocketResultMap.get("isAlreadyResq") != null) {} else {// 如果没有发送请求,那就是直接付款失败}// 响应的返回来源,由农行提供的String respSource = null;// 如果应答对象有值if (erpSocketResultMap.get("responseEntity") != null) {// 公共应答字段ResponseBaseEntity response = (ResponseBaseEntity) erpSocketResultMap.get("responseEntity");// 返回来源respSource = response.getRespSource();// 响应时间response.getRespTime();// 响应描述信息response.getRespInfo();// 响应拓展信息response.getRxtInfo();// 转换成具体的接口响应对象CFRT02ResponseDTO cfrt02Response = (CFRT02ResponseDTO) erpSocketResultMap.get("responseEntity");if (cfrt02Response.getCorp() != null) {// 是否落地处理cfrt02Response.getCorp().getWaitFlag();}} else if (erpSocketResultMap.get("isAlreadyResq") != null) {// 返回对象为空,但发送请求了,可能是因为响应超时,这里要注意,可能交易成功了,所以最好在业务上标记为已成功发送请求,后面在调用其他接口查询交易状态,不可轻易认为是交易失败}// 错误信息if (erpSocketResultMap.get("errorMessage") != null) {erpSocketResultMap.get("errorMessage").toString();}// 根据返回来源判断是否交易失败, -1 表明没有发送请求,交易明确是失败,其他值,就通过查询接口查询确定本次交易状态if (StringUtils.equals("-1", respSource)) {}}

到这里就完事了,下面是我的测试结果


请求报文

应答报文(随机填了些虚假信息,所以失败了)


 其他类


省份枚举类 

/*** 农行企业银行-省区代码* */
public enum ProvinceCodeEnum {Tianjin("02", "天津市"),Shanghai("03", "上海"),Shanxi("04", "山西省"),Neimenggu("05", "内蒙古"),Liaoning("06", "辽宁省"),Jilin("07", "吉林省"),Heilongjiang("08", "黑龙江"),Jiangsu("10", "江苏省"),Beijing("11", "北京市"),Anhui("12", "安徽省"),Fujian("13", "福建省"),Jiangxi("14", "江西省"),Shandong("15", "山东省"),Henan("16", "河南省"),Hubei("17", "湖北省"),Hunan("18", "湖南省"),Zhejiang("19", "浙江省"),Guangxi("20", "广西区"),Hainan("21", "海南省"),Sichuan("22", "四川省"),Guizhou("23", "贵州省"),Yunnan("24", "云南省"),Xizang("25", "西藏区"),Shaanxi("26", "陕西省"),Gansu("27", "甘肃省"),Qinghai("28", "青海省"),Ningxia("29", "宁夏区"),Xinjiang("30", "新疆区"),Chongqing("31", "重庆市"),Dalian("34", "大连市"),Qingdao("38", "青岛市"),Ningbo("39", "宁波市"),Xiamen("40", "厦门市"),ShenZhen("41", "深圳市"),Guangdong("44", "广东省"),Hebei("50", "河北省"),Taiwan("71", "台湾省"),Trade("81", "营业部"),Xianggang("97", "香港"),Aomen("98", "澳门"),headOffice("99", "总行");private String code;private String title;ProvinceCodeEnum(String code, String title) {this.code = code;this.title = title;}public String code() {return this.code;}public String title() {return this.title;}
}

货币枚举类

/*** 农行企业银行-货币代码* */
public enum CurrencyEnum {COMPOSITE("00", "复合币种"),CNY("01", "CNY 人民币"),GBP("12", "GBP 英镑"),HKD("13", "HKD 港币"),USD("14", "USD 美元"),CHF("15", "CHF 瑞士法郎"),SGD("18", "SGD 新加坡元"),SEK("21", "SEK 瑞典克郎"),DKK("22", "DKK 丹麦克郎"),NOK("23", "NOK 挪威克郎"),JPY("27", "JPY 日元"),CAD("28", "CAD 加拿大元"),AUD("29", "AUD 澳大利亚元"),EUR("38", "EUR 欧元"),MOP("81", "MOP 澳门币");private String code;private String title;CurrencyEnum(String code, String title) {this.code = code;this.title = title;}public String code() {return this.code;}public String title() {return this.title;}
}

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

千里黄云白日曛,北风吹雁雪纷纷

这篇关于java农业银行-企业银行ERP接口开发(2-接口对接)(汇兑-单笔对公、对私)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/719858

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

Hadoop企业开发案例调优场景

需求 (1)需求:从1G数据中,统计每个单词出现次数。服务器3台,每台配置4G内存,4核CPU,4线程。 (2)需求分析: 1G / 128m = 8个MapTask;1个ReduceTask;1个mrAppMaster 平均每个节点运行10个 / 3台 ≈ 3个任务(4    3    3) HDFS参数调优 (1)修改:hadoop-env.sh export HDFS_NAMENOD

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory