个人上一篇博客园【 采用加载XML文件的形式组转通信报文,经过相似EL表示的方式赋值】可以解决绝大部分报文组装功能,可是有一种状况,它不能适用,就是当组装响应报文是,是查询某个表的前n条记录是,它不能自动控制循环体的个数。好比银行转行汇款中,查询历史交易信息(收款人姓名、地址、帐号、联系点)的前5条时,我上一篇博客的方案是不可以解决这样的接口报文采用模板模式。java
那么,对于报文Array、List的报文,能不能采用模板模式呢?正则表达式
答案必须是YES!缓存
简单介绍一下个人方案。我定义了一个<FOR-EACH KEY="hist"> ..(循环重复的内容)..</FOR-EACH>标签,标签以内的部分是循环重复的内容。简单的举个例子模板以下:app
<BODY> <LIST> <FOR-EACH KEY="hist"> <STRUCT> <NAME>${name}</NAME> <AGE>${age}</AGE> </STRUCT> </FOR-EACH> <LIST> </BODY>
咱们期待可以产生的报文像下面同样。ide
<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表达式的地方所有被空白替换了。单元测试
接下来就开始去实现了。测试
首先,文件读取和缓存部分和此前的没有变化,仍然采用原来的方案便可。
ui
其次,考虑一下XmlMessageFormat类的Foramt方法,其中数据部分再也不是Map<String,String>了。由于已经包含了List结构了,所以,修改为以下所示:spa
/*** * * @author huangqian(huangqian866@163.com) * */ public class XmlMessageFormat implements Formatable { /*** * XML中值替换的正则表达式模式,例如${name} */ public static final String REGEX_PATTERN = "(\\$\\{)(\\w+)(\\})"; @Override public String format(Map<String,Object> data,String srcXmlMessage) { Pattern pattern = Pattern.compile(REGEX_PATTERN); Matcher matcher = pattern.matcher(srcXmlMessage); while(matcher.find()){ String key = matcher.group(2); String val = (String)data.get(key); val = val==null ? "" : val.trim(); String replaceRegexPattern = "\\$\\{"+key+"\\}"; srcXmlMessage = srcXmlMessage.replaceAll(replaceRegexPattern, val); } return srcXmlMessage; } }
接下来,考虑ForeachFormat的设计了。在这里关键点是FOR-EACH标签的正则表达式的设计了,好好分析一下,我须要FOR-EACH标签包含的所有内容、KEY的值、还有FOR-EACH孩子部分是循环的部分,此外还要考虑空格的问题,这样我就能够得出正则表达式:(<FOR-EACH[\\s]+KEY=\")(\\w+)(\"\\s*>)([\\s\\S]+)(</FOR-EACH>)
接下来就是去实现了,废话很少说,直接上代码:
/*** * * @author 零下三度(huangqian866@163.com) * */ public class ForeachFormat implements Formatable { /*** * FOR-EACH标签的捕获正则表达式 */ private static final String FOREACH_REGEX_PATTERN = "(<FOR-EACH[\\s]+KEY=\")(\\w+)(\"\\s*>)([\\s\\S]+)(</FOR-EACH>)"; /*** * XML中<FOR-EACH></FOR-EACH>的格式化 */ @Override public String format(Map<String, Object> data, String srcXmlMessage) { Pattern pattern = Pattern.compile(FOREACH_REGEX_PATTERN); Matcher matcher = pattern.matcher(srcXmlMessage); String newXmlPart; while (matcher.find()) { String oldXml = matcher.group(); System.out.println("oldXml:"+oldXml); String key = matcher.group(2); System.out.println("key:"+key); String foreachContent = 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); } return srcXmlMessage; } /*** * 格式化foreach中的内容 * * @param list * @param foreachXml * @return */ private String foreachFormat(List<Map<String, Object>> list, String foreachXml) { StringBuilder xml = new StringBuilder(); XmlMessageFormat xmlFormat = new XmlMessageFormat(); String formatRstStr; if(list != null){ for (Map<String, Object> item : list) { formatRstStr = xmlFormat.format(item, foreachXml); xml.append(formatRstStr); } } return xml.toString(); } public static void main(String[] args) { String xml = "<?xml version=\"1.0\" encoding=\"GBK\"?>" + "<Transaction>" + "<BODY>" + "<YTD_IP>${YTD_IP}</YTD_IP>" + "<FOR-EACH KEY=\"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>"; ForeachFormat format = new ForeachFormat(); format.format(null, xml); } }
如今功能都有了,调用顺序很重要,先要去替换FOR-EACH部分,不然FOR-EACH部分的相似EL表达式的地方所有被空格替换了,看看MessageFactory的代码吧:
/*** * * @author 零下三度(huangqian866@163.com) * */ public class MessageFactory { private static final Formatable xmlMessageFormat = new XmlMessageFormat(); private static final Formatable foreachFormat = new ForeachFormat(); public static String creatXmlMessage(String tranNo,HashMap<String,Object> data) throws NoSuchTranException { if(tranNo == null || tranNo.trim().isEmpty()){//交易号为null || isEmpty throw new NoSuchTranException("tranNo is Empty or null"); } String content = FileCache.getInstance().getData(tranNo.trim()); if(content == null){//未能在缓存中找到以tranNo为key的数据 throw new NoSuchTranException("not found file content in FileCache"); } //处理foreach content = foreachFormat.format(data, content); return xmlMessageFormat.format(data, content); } }
好了,都写玩了,上一个creatXmlMessage方法的单元测试
@Test public void test2() { String tranNo = "1008"; HashMap<String,Object> data = new HashMap<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 = new HashMap<String,Object>(); List<HashMap> list = new ArrayList<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 = new HashMap<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); String xml = MessageFactory.creatXmlMessage(tranNo, data); System.out.println(xml); }
接下来是报文的模板1008.xml
<?xml version="1.0" encoding="GBK"?> <Transaction> <BODY> <YTD_IP>${YTD_IP}</YTD_IP> <FOR-EACH KEY="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>
运行测试用例得出结果以下(省略中其余地方的输出):
<?xml version="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了,到如今,基本上支持绝大部分的报文的组装工做了。
由于本身在公司负责接口这一块,遇到的问题多了,就产生那么点点想法,出差在外,看完球心血来潮,噼噼啪啪就写下来了,留给之后的本身,也但愿可以对到遇到相似问题的朋友们有一点点帮助。
声明一下:大量的正则替换是至关影响性能的,实际应用有待商榷,目前正在寻找其余方式替换“正则替换”的工做。