我的上一篇博客园【 采用加载XML文件的形式组转通讯报文,通过类似EL表示的方式赋值】能够解决绝大部分报文组装功能,但是有一种情况,它不能适用,就是当组装响应报文是,是查询某个表的前n条记录是,它不能自动控制循环体的个数。比如银行转行汇款中,查询历史交易信息(收款人姓名、地址、账号、联系点)的前5条时,我上一篇博客的方案是不能够解决这样的接口报文采用模板模式。
那么,对于报文Array、List的报文,能不能采用模板模式呢?
答案必须是YES!
简单介绍一下我的方案。我定义了一个<FOR-EACH KEY="hist"> ..(循环重复的内容)..</FOR-EACH>标签,标签之内的部分是循环重复的内容。简单的举个例子模板如下:
<BODY> <LIST> <FOR-EACHKEY="hist"> <STRUCT> <NAME>${name}</NAME> <AGE>${age}</AGE> </STRUCT> </FOR-EACH> <LIST> </BODY>
我们期待能够产生的报文像下面一样。
<BODY> <LIST> <STRUCT> <NAME>卡卡</NAME> <AGE>30</AGE> </STRUCT> <STRUCT> <NAME>C罗</NAME> <AGE>28</AGE> </STRUCT> <LIST> </BODY>
接下来详细介绍一下我的设计方案。在这里我采用正则表达式去捕获FOR-EACH标签、KEY的值、循环部分的内容。KEY是在报文工厂的数据Map结构中,循环数据List的key。循环部分可以采用上篇博客的XmlMessageFormat的format方法格式循环部分的字符串,然后用格式化的字符串替换原来处理FOR-EACH包含的内容。
注意:这里应该先替换foreach部分的内容,否则foreach部分的类似EL表达式的地方全部被空白替换了。
接下来就开始去实现了。
首先,文件读取和缓存部分和此前的没有变化,仍然采用原来的方案即可。
其次,考虑一下XmlMessageFormat类的Foramt方法,其中数据部分不再是Map<String,String>了。因为已经包含了List结构了,因此,修改成如下所示:
/*** * *@authorhuangqian(huangqian866@163.com) * */ publicclassXmlMessageFormatimplementsFormatable{ /*** *XML中值替换的正则表达式模式,例如${name} */ publicstaticfinalStringREGEX_PATTERN="(\\$\\{)(\\w+)(\\})"; @Override publicStringformat(Map<String,Object>data,StringsrcXmlMessage){ Patternpattern=Pattern.compile(REGEX_PATTERN); Matchermatcher=pattern.matcher(srcXmlMessage); while(matcher.find()){ Stringkey=matcher.group(2); Stringval=(String)data.get(key); val=val==null?"":val.trim(); StringreplaceRegexPattern="\\$\\{"+key+"\\}"; srcXmlMessage=srcXmlMessage.replaceAll(replaceRegexPattern,val); } returnsrcXmlMessage; } }
接下来,考虑ForeachFormat的设计了。在这里关键点是FOR-EACH标签的正则表达式的设计了,好好分析一下,我需要FOR-EACH标签包含的全部内容、KEY的值、还有FOR-EACH孩子部分是循环的部分,此外还要考虑空格的问题,这样我就可以得出正则表达式:(<FOR-EACH[\\s]+KEY=\")(\\w+)(\"\\s*>)([\\s\\S]+)(</FOR-EACH>)
接下来就是去实现了,废话不多说,直接上代码:
/*** * *@author零下三度(huangqian866@163.com) * */ publicclassForeachFormatimplementsFormatable{ /*** *FOR-EACH标签的捕获正则表达式 */ privatestaticfinalStringFOREACH_REGEX_PATTERN="(<FOR-EACH[\\s]+KEY=\")(\\w+)(\"\\s*>)([\\s\\S]+)(</FOR-EACH>)"; /*** *XML中<FOR-EACH></FOR-EACH>的格式化 */ @Override publicStringformat(Map<String,StringsrcXmlMessage){ Patternpattern=Pattern.compile(FOREACH_REGEX_PATTERN); Matchermatcher=pattern.matcher(srcXmlMessage); StringnewXmlPart; while(matcher.find()){ StringoldXml=matcher.group(); System.out.println("oldXml:"+oldXml); Stringkey=matcher.group(2); System.out.println("key:"+key); StringforeachContent=matcher.group(4); System.out.println("foreachContent:"+foreachContent); List<Map<String,Object>>list=(List<Map<String,Object>>)data.get(key); newXmlPart=foreachFormat(list,foreachContent); srcXmlMessage=srcXmlMessage.replace(oldXml,newXmlPart); } returnsrcXmlMessage; } /*** *格式化foreach中的内容 * *@paramlist *@paramforeachXml *@return */ privateStringforeachFormat(List<Map<String,Object>>list,StringforeachXml){ StringBuilderxml=newStringBuilder(); XmlMessageFormatxmlFormat=newXmlMessageFormat(); StringformatRstStr; if(list!=null){ for(Map<String,Object>item:list){ formatRstStr=xmlFormat.format(item,foreachXml); xml.append(formatRstStr); } } returnxml.toString(); } publicstaticvoidmain(String[]args){ Stringxml="<?xmlversion=\"1.0\"encoding=\"GBK\"?>" +"<Transaction>"+"<BODY>"+"<YTD_IP>${YTD_IP}</YTD_IP>" +"<FOR-EACHKEY=\"foreach\">"+"<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>"+"</FOR-EACH>" +"</BODY>"+"</Transaction>"; ForeachFormatformat=newForeachFormat(); format.format(null,xml); } }
现在功能都有了,调用顺序很重要,先要去替换FOR-EACH部分,否则FOR-EACH部分的类似EL表达式的地方全部被空格替换了,看看MessageFactory的代码吧:
/*** * *@author零下三度(huangqian866@163.com) * */ publicclassMessageFactory{ privatestaticfinalFormatablexmlMessageFormat=newXmlMessageFormat(); privatestaticfinalFormatableforeachFormat=newForeachFormat(); publicstaticStringcreatXmlMessage(StringtranNo,HashMap<String,Object>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"); } //处理foreach content=foreachFormat.format(data,content); returnxmlMessageFormat.format(data,content); } }
好了,都写玩了,上一个creatXmlMessage方法的单元测试
@Test publicvoidtest2(){ StringtranNo="1008"; HashMap<String,Object>data=newHashMap<String,Object>(); 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"); HashMap<String,Object>item=newHashMap<String,Object>(); List<HashMap>list=newArrayList<HashMap>(); item.put("ZHUCZJ","123"); item.put("HANGYL","农业"); item.put("ZZZCDZ","北京市东城区北京市东城区"); item.put("SFRENMC","阿毛"); item.put("SUSDDM","320100"); item.put("FRENMC","111"); list.add(item); HashMap<String,Object>item1=newHashMap<String,Object>(); item1.put("ZHUCZJ","456"); item1.put("HANGYL","采矿"); item1.put("ZZZCDZ","上海"); item1.put("SFRENMC","阿狗"); item1.put("SUSDDM","320100"); item1.put("FRENMC","111"); list.add(item1); data.put("STUDET",list); Stringxml=MessageFactory.creatXmlMessage(tranNo,data); System.out.println(xml); }
接下来是报文的模板1008.xml
<?xmlversion="1.0"encoding="GBK"?> <Transaction> <BODY> <YTD_IP>${YTD_IP}</YTD_IP> <FOR-EACHKEY="STUDET"> <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> </FOR-EACH> </BODY> </Transaction>
运行测试用例得出结果如下(省略中其他地方的输出):
<?xmlversion="1.0"encoding="GBK"?> <Transaction> <BODY> <YTD_IP>66.12.18.20</YTD_IP> <LOOP> <CODERECORD> <ZHUCZJ>123</ZHUCZJ> <HANGYL>农业</HANGYL> <ZZZCDZ>北京市东城区北京市东城区</ZZZCDZ> <SFRENMC>阿毛</SFRENMC> <SUSDDM>320100</SUSDDM> <FRENMC>111</FRENMC> <SJZGZH></SJZGZH> </CODERECORD> <TRANCODE></TRANCODE> </LOOP> <LOOP> <CODERECORD> <ZHUCZJ>456</ZHUCZJ> <HANGYL>采矿</HANGYL> <ZZZCDZ>上海</ZZZCDZ> <SFRENMC>阿狗</SFRENMC> <SUSDDM>320100</SUSDDM> <FRENMC>111</FRENMC> <SJZGZH></SJZGZH> </CODERECORD> <TRANCODE></TRANCODE> </LOOP> </BODY> </Transaction>
OK了,到现在,基本上支持绝大部分的报文的组装工作了。
因为自己在公司负责接口这一块,遇到的问题多了,就产生那么点点想法,出差在外,看完球心血来潮,噼噼啪啪就写下来了,留给以后的自己,也希望能够对到遇到类似问题的朋友们有一点点帮助。
声明一下:大量的正则替换是相当影响性能的,实际应用有待商榷,目前正在寻找其他方式替换“正则替换”的工作。