我的上一篇博客园【 采用加载XML文件的形式组转通讯报文,通过类似EL表示的方式赋值】能够解决绝大部分报文组装功能,但是有一种情况,它不能适用,就是当组装响应报文是,是查询某个表的前n条记录是,它不能自动控制循环体的个数。比如银行转行汇款中,查询历史交易信息(收款人姓名、地址、账号、联系点)的前5条时,我上一篇博客的方案是不能够解决这样的接口报文采用模板模式。
那么,对于报文Array、List的报文,能不能采用模板模式呢?
答案必须是YES!
简单介绍一下我的方案。我定义了一个<FOR-EACH KEY="hist"> ..(循环重复的内容)..</FOR-EACH>标签,标签之内的部分是循环重复的内容。简单的举个例子模板如下:
<BODY>
<LIST>
<FOR-EACH KEY="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结构了,因此,修改成如下所示:
/***
*
* @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了,到现在,基本上支持绝大部分的报文的组装工作了。
因为自己在公司负责接口这一块,遇到的问题多了,就产生那么点点想法,出差在外,看完球心血来潮,噼噼啪啪就写下来了,留给以后的自己,也希望能够对到遇到类似问题的朋友们有一点点帮助。
声明一下:大量的正则替换是相当影响性能的,实际应用有待商榷,目前正在寻找其他方式替换“正则替换”的工作。