虽然有Json,但是接口报文大部分时候采用的XML。接口开发中最麻烦的是接口报文不断变化,使用数据库记录报文结构固然可以应对接口的变化,但是有些时候缓存无法设计,比如如下这个报文的BODY部分:
<BODY> <LOOP> <NAME>jerry</NAME> <AGE>21</AGE> </LOOP> <LOOP> <NAME>mark</NAME> <AGE>22</AGE> </LOOP> </BODY>
在上面的报文BODY部分,两个LOOP存在的情况,无法通过其他属性来标识两个LOOP的差异属性,在做缓存的时候,无法通过key去区分这两个LOOP的缓存信息。还有一种情况
<BODY> <LOOP_NUM>2<LOOP_NUM> <LOOP> <CUST_NAME>阿毛</CUST_NAME> <CUST_IDCARD_NO>11111111111111111</CUST_IDCARD_NO> </LOOP> <LOOP> <AGENT_NAME>阿狗</CUST_NAME> <AGENT_IDCARD_NO>2222222222222222</CUST_IDCARD_NO> <TRAN_TIME>20140411</TRAN_TIME> </LOOP> </BODY>
两个Loop里面的内容还不一致。这样的报文结构设计,哎……当我们去从别人的系统中获取数据或者提交数据,也会碰到,这样的报文结构如果存入数据库中,如何缓存到内存中呢?我没有找到比较好的方法。于是我就采用了最笨的方法是用StringBuilder(或者StringBuffer去拼接xml字符串),当然能够完成工作,但是每个交易的报文拼接要写几百行的代码,而且不易维护,如果别人来维护我的代码,我估计他要疯了。
于是我开始寻找一些方法,去解决这些问题。后面我想到了一个方案,不知道好不好,但是能够解决我的问题,而且不用修改代码了。
我的方案是每个交易的报文写成一个xml文件,采用类似EL表达式一样的法则去给报文赋值。如下所示:
<BODY> <LOOP_NUM>${LOOP_NUM}<LOOP_NUM> <LOOP> <CUST_NAME>${CUST_NAME}</CUST_NAME> <CUST_IDCARD_NO>${CUST_IDCARD_NO}</CUST_IDCARD_NO> </LOOP> <LOOP> <AGENT_NAME>${AGENT_NAME}</CUST_NAME> <AGENT_IDCARD_NO>${AGENT_IDCARD_NO}</CUST_IDCARD_NO> <TRAN_TIME>${TRAN_TIME}</TRAN_TIME> </LOOP> </BODY>
这样的好处有如下两点:
1.修改接口报文不需要修改代码或者数据库了,直接改xml文件。
2.接口报文赋值修改只需要将${name}修改为${name_1}.
对于其他竞争公司经常修改报文故意刁难自己的时候,不要担心这个他们疯狂的接口变更了!!让他们自作孽去吧!
闲话不多说了。开始设计。
首先应该读取xml文件并缓存。
/*** * *@author零下三度(huangqian866@163.com) * */ publicclassFileIoUtils{ /*** *默认类路径 */ publicstaticfinalStringDEFAULT_CLASS_PATH=System .getProperty("user.dir")+File.separator+"src"; publicstaticfinalStringCHARSET="UTF-8"; /*** *文件过滤器,过滤出xml文件 */ privatestaticFileFilterfilter=newFileFilter(){ @Override publicbooleanaccept(Filepathname){ if(pathname.isFile() &&pathname.getName().toLowerCase().endsWith(".xml")){ returntrue; } returnfalse; } }; /*** *获取指定目录下的所有xml文件的绝对路径 * *@paramdirPath *目录路径 *@return */ publicstaticList<String>getXmlFileAbspath(StringdirPath){ ArrayList<String>fileNames=newArrayList<String>(); if(dirPath==null||dirPath.trim().isEmpty()){ dirPath=DEFAULT_CLASS_PATH; } Filedir=newFile(dirPath); if(dir.exists()){//文件存在 if(dir.isDirectory()){//文件为目录路径 File[]xmlFiles=dir.listFiles(filter); for(Filefile:xmlFiles){ fileNames.add(file.getAbsolutePath()); System.out.println(file.getAbsolutePath()); } } } returnfileNames; } /*** *根据文件路径,获取文件内容 * *@paramfilePath *@return */ publicstaticStringgetContent(StringfilePath){ Stringcontext=""; try{ Filefile=newFile(filePath); if(file.exists()&&file.isFile()){ FileInputStreamfis=newFileInputStream(file); bytebuffer[]=newbyte[fis.available()]; fis.read(buffer,fis.available()); context=newString(buffer,CHARSET); fis.close(); } }catch(FileNotFoundExceptione){ e.printStackTrace(); }catch(IOExceptione){ e.printStackTrace(); } returncontext; } }
缓存类代码如下:
/*** * *@author零下三度(huangqian866@163.com) * */ publicclassFileIoUtils{ /*** *默认类路径 */ publicstaticfinalStringDEFAULT_CLASS_PATH=System .getProperty("user.dir")+File.separator+"src"; publicstaticfinalStringCHARSET="UTF-8"; /*** *文件过滤器,过滤出xml文件 */ privatestaticFileFilterfilter=newFileFilter(){ @Override publicbooleanaccept(Filepathname){ if(pathname.isFile() &&pathname.getName().toLowerCase().endsWith(".xml")){ returntrue; } returnfalse; } }; /*** *获取指定目录下的所有xml文件的绝对路径 * *@paramdirPath *目录路径 *@return */ publicstaticList<String>getXmlFileAbspath(StringdirPath){ ArrayList<String>fileNames=newArrayList<String>(); if(dirPath==null||dirPath.trim().isEmpty()){ dirPath=DEFAULT_CLASS_PATH; } Filedir=newFile(dirPath); if(dir.exists()){//文件存在 if(dir.isDirectory()){//文件为目录路径 File[]xmlFiles=dir.listFiles(filter); for(Filefile:xmlFiles){ fileNames.add(file.getAbsolutePath()); System.out.println(file.getAbsolutePath()); } } } returnfileNames; } /*** *根据文件路径,获取文件内容 * *@paramfilePath *@return */ publicstaticStringgetContent(StringfilePath){ Stringcontext=""; try{ Filefile=newFile(filePath); if(file.exists()&&file.isFile()){ FileInputStreamfis=newFileInputStream(file); bytebuffer[]=newbyte[fis.available()]; fis.read(buffer,CHARSET); fis.close(); } }catch(FileNotFoundExceptione){ e.printStackTrace(); }catch(IOExceptione){ e.printStackTrace(); } returncontext; } }
完成文件读取和内容缓存,接下来就是格式化替代的工作了。java中有一个自带的MessageFormat类,其麻烦支出在于参数必须按照顺序给,否则就乱了。接口开发的时候尽量要使用者方便一些,所以在这里采用正则表达式去替换。
格式化类代码如下:
/*** * *@authorhuangqian(huangqian866@163.com) * */ publicclassXmlMessageFormatimplementsFormatable{ /*** *XML中值替换的正则表达式模式,例如${name} */ publicstaticfinalStringREGEX_PATTERN="(\\$\\{)(\\w+)(\\})"; @Override publicStringformat(Map<String,String>data,StringsrcXmlMessage){ Patternpattern=Pattern.compile(REGEX_PATTERN); Matchermatcher=pattern.matcher(srcXmlMessage); while(matcher.find()){ Stringkey=matcher.group(2); Stringval=data.get(key); val=val==null?"":val.trim(); StringreplaceRegexPattern="\\$\\{"+key+"\\}"; srcXmlMessage=srcXmlMessage.replaceAll(replaceRegexPattern,val); } returnsrcXmlMessage; } }
在完成读取xml文件和字符串格式化类后,接下来的就是报文工厂了,代码如下:
/*** * *@author零下三度(huangqian866@163.com) * */ publicclassMessageFactory{ privatestaticfinalFormatableformat=newXmlMessageFormat(); publicstaticStringcreatXmlMessage(StringtranNo,HashMap<String,String>data)throwsNoSuchTranException{ if(tranNo==null||tranNo.trim().isEmpty()){//交易号为null||isEmpty thrownewNoSuchTranException("tranNoisEmptyornull"); } Stringcontent=FileCache.getInstance().getData(tranNo.trim()); if(content==null){//未能在缓存中找到以tranNo为key的数据 thrownewNoSuchTranException("notfoundfilecontentinFileCache"); } returnformat.format(data,content); } }
好了。以上是基础的架子,写出来了,还没有完,必须单元测试一下。这里就不详细测试各个方法了,只写一个creatXmlMessage方法的测试用例。
/*** * *@author零下三度(huangqian866@163.com) * */ publicclassMessageFactoryTest{ @Test publicvoidtest(){ StringtranNo="1009"; HashMap<String,String>data=newHashMap<String,String>(); data.put("SEQ_NO","2014022800010502"); data.put("TRAN_DATE","20140418"); data.put("SERVICE_ID","Y001"); data.put("BANK_CODE","9901"); data.put("TRAN_TIME","095104"); data.put("CHANNEL_ID","04"); data.put("USER_ID","001"); data.put("BUSINESS_ID","Y001"); data.put("TYPE","Z2"); data.put("YTD_IP","66.12.18.20"); data.put("LOOPNUM","1"); data.put("ZHUCZJ","1111111111.11"); data.put("HANGYL","0"); data.put("ZZZCDZ","北京市东城区北京市东城区"); data.put("SFRENMC","阿毛"); data.put("SUSDDM","320100"); data.put("FRENMC","111"); Stringxml=MessageFactory.creatXmlMessage(tranNo,data); System.out.println(xml); } }
<?xmlversion="1.0"encoding="GBK"?> <Transaction> <BODY> <YTD_IP>${YTD_IP}</YTD_IP> <QUEUE_NO>${QUEUE_NO}</QUEUE_NO> <LOOPNUM>${LOOPNUM}</LOOPNUM> <LOOP> <CODERECORD> <ZHUCZJ>${ZHUCZJ}</ZHUCZJ> <HANGYL>${HANGYL}</HANGYL> <ZZZCDZ>${ZZZCDZ}</ZZZCDZ> <SFRENMC>${SFRENMC}</SFRENMC> <SUSDDM>${SUSDDM}</SUSDDM> <FRENMC>${FRENMC}</FRENMC> <SJZGZH>${SJZGZH}</SJZGZH> </CODERECORD> <TRANCODE></TRANCODE> </LOOP> <TYPE>Z2</TYPE> </BODY> <HEAD> <EXT_HEAD> <BUSINESS_ID>${BUSINESS_ID}</BUSINESS_ID> <MAC_INDEX></MAC_INDEX> <MAC_VALUE></MAC_VALUE> </EXT_HEAD> <TRAN_TERM></TRAN_TERM> <USER_ID>${USER_ID}</USER_ID> <CHANNEL_ID>${CHANNEL_ID}</CHANNEL_ID> <TRAN_TIME>${TRAN_TIME}</TRAN_TIME> <BANK_CODE>${BANK_CODE}</BANK_CODE> <SERVER_ID></SERVER_ID> <AUTH_ID></AUTH_ID> <AUTH_CONTEXT></AUTH_CONTEXT> <SERVICE_ID>${SERVICE_ID}</SERVICE_ID> <TRAN_DATE>${TRAN_DATE}</TRAN_DATE> <SEQ_NO>${SEQ_NO}</SEQ_NO> </HEAD> </Transaction>
运行测试用例,测试结果如下:
<?xml version="1.0" encoding="GBK"?>
<Transaction>
<BODY>
<YTD_IP>66.12.18.20</YTD_IP>
<QUEUE_NO></QUEUE_NO>
<LOOPNUM>1</LOOPNUM>
<LOOP>
<CODERECORD>
<ZHUCZJ>1111111111.11</ZHUCZJ>
<HANGYL>0</HANGYL>
<ZZZCDZ>北京市东城区北京市东城区</ZZZCDZ>
<SFRENMC>阿毛</SFRENMC>
<SUSDDM>320100</SUSDDM>
<FRENMC>111</FRENMC>
<SJZGZH></SJZGZH>
</CODERECORD>
<TRANCODE></TRANCODE>
</LOOP>
<TYPE>Z2</TYPE>
</BODY>
<HEAD>
<EXT_HEAD>
<BUSINESS_ID>Y001</BUSINESS_ID>
<MAC_INDEX></MAC_INDEX>
<MAC_VALUE></MAC_VALUE>
</EXT_HEAD>
<TRAN_TERM></TRAN_TERM>
<USER_ID>001</USER_ID>
<CHANNEL_ID>04</CHANNEL_ID>
<TRAN_TIME>095104</TRAN_TIME>
<BANK_CODE>9901</BANK_CODE>
<SERVER_ID></SERVER_ID>
<AUTH_ID></AUTH_ID>
<AUTH_CONTEXT></AUTH_CONTEXT>
<SERVICE_ID>Y001</SERVICE_ID>
<TRAN_DATE>20140418</TRAN_DATE>
<SEQ_NO>2014022800010502</SEQ_NO>
</HEAD>
</Transaction>
完成了功能。
思考
这里有一个问题。无法完成foreach的能。比如说,我要查询学生表的前20条记录的响应报文,这个功能现在还不能根据实际个数去循环组转报文,这个是后续努力的方向。如果这个实现了,那么组装报文就一切都很简单了。