使用 BizTalk Server 2013 R2 进行集成学习





5.00/5 (1投票)
在生成管道分隔的平面文件时遇到的问题和发现的学习
引言
最近,我们参与了一个 BizTalk 项目,该项目要求生成管道分隔的平面文件 (FF)。由于此任务的各种要求,我们学到了很多东西。我希望在这篇文章中分享一些经验,这些经验可能有助于他人在开发过程中。
首先,我们将简要介绍作为此任务一部分要实现的需求的不同方面。
- 订单 XML 文件来自 EOM(企业订单管理)系统,必须将其转换为管道分隔的 FF 格式,并带有特定的映射和规则
- 平面文件需要为每个发票及其行重复行。例如,如果订单 XML 有 2 个发票,每个发票有 3 行,那么平面文件中有 2 个标头,每个标头下有 2 个行项目
Input XML File: Output Flat file: <Invoice1> HEADER|field1|field2….. <Line1> LINE|field1|field2.. <Line2> LINE|field1|field2… </Invoice1> <Invoice2> HEADER|field1|field2….. <Line1> LINE|field1|field2.. <Line2> LINE|field1|field2.. </Invoice2>
- 平面文件需要在所有内容之前,将文件放置在客户端位置之前,在顶部有一个标头元素
- 需要为销售和调整在不同的位置生成单独的平面文件
- 调整的管道分隔格式需要具有动态内容而不是固定元素。
- 作为 PCI DSS 合规性的一部分,信用卡号在传输过程中应加密,仅在将文件放置在客户端位置时解密
- 处理平面文件中的可选元素,需要将销售架构中的所有 FF XSD 元素都设置为 Choice,
minOccurs=0
和maxoccurs
为 unbounded - FF 中的标头级别项目需要计算不同行级别值(例如金额、税金等)的总和或平均值
- FF 反汇编器和汇编器命令行工具已用于测试 FF 架构是否没有任何问题
我们将详细介绍这些功能,并解释如何使用 BizTalk 2013 R2 解决这些问题。
订单 XML 文件来自 EOM(企业订单管理)系统,必须将其转换为管道分隔的 FF 格式,并带有特定的映射和规则
我们使用了一种中间规范 XML 格式来转换输入订单 XML 文件,然后再转换为 FF。业务逻辑和循环在此转换中实现,并存储为规范 XML 字段。在将规范转换为输出 FF 期间,其思想是进行直接的一对一映射,其中没有太多的业务逻辑。规范格式还有助于将一个或多个源或目标系统添加到要集成的系统。在这些情况下,我们希望将源转换为规范和/或将规范转换为目标格式。如果未使用规范架构方法,我们需要维护不同的映射以在不同的源和目标格式之间进行转换,这很麻烦。
根据 BizTalk 实践,规范到平面文件转换需要将 FF 架构指定在发送端口中以生成平面文件。
订单 XML 文件来自 EOM(企业订单管理)系统,必须将其转换为管道分隔的 FF 格式,并带有特定的映射和规则。
FF 需要为每个发票及其行重复行
我们使用 XSLT 进行规范到输出 FF 格式的整个转换过程。因此,在 XSLT 中实现这一点相对简单,因为我们可以使用 for each
循环遍历输入 XML 中的每个发票节点,并为每个输入发票和行重复形成输出元素。
<xsl:for-each select="// Invoices">
<Element1></Element1>
<Element2></Element2>
…
<xsl:for-each select="// Invoices/Line">
<LineElement1></LineElement1>
<LineElement2></LineElement2>
…
FF 需要在所有内容之前,在顶部有一个标头元素
Sample Header:
<Header download_id="SALE_20180322121907.txt" deployment_name="ORDER_SALE" download_time="IMMEDIATE" />
这通过在 BizTalk FF 架构中创建一个可选的 Header 元素来实现。这可以使用 FFAsm.exe 测试 XML 到平面文件的生成,或者使用 FFDAsm.exe 测试 FF 到 XML 的转换。我们将在后面的部分中详细介绍这些工具以及如何使用它们。
需要为销售和调整在不同的位置生成单独的平面文件
我们在规范标头中添加了一个额外的字段 InvoiceType
,它将具有值“Sales
”或“Adjustment
”。输入 XML 到规范 XML 转换将根据输入文件字段值适当地填充此值。此外,InvoiceType
在规范架构中提升,以便可以在发送端口筛选器中查看它。一旦查看,我们就可以在发送端口中检查此条件,并使用它在不同的位置生成文件。
SendPortAdjustment - Filter Condition: InvoiceType == Adjustment
SendPortSale - Filter Condition: InvoiceType == Sale
调整的管道分隔格式需要具有动态内容而不是固定元素。
我们要求以以下格式生成调整平面文件
RUN_SQL|INSERT INTO TABLE(FIELD1, FIELD2, ….) VALUES (FIELDVAL1, FIELDVAL2, …)
RUN_SQL|INSERT INTO TABLE(FIELD1, FIELD2, ….) VALUES (FIELDVAL1, FIELDVAL2, …)
为此,我们创建了 FF 架构,其元素具有 tag_name=”RUN_SQL|”
。因此,它将构造以 RUN_SQL
作为起始标签,并在管道分隔符后紧接着 INSERT
命令的 FF。我们使用 XSLT 和 C# 构建值为 RUN_SQL|INSERT INTO TABLE
... 并将动态构建的 insert
查询分配给 XSD 元素。构建的架构 XML 可以使用管道工具 FFAsm.exe/FFDAsm.exe 转换为/从 FF。
作为 PCI DSS 合规性的一部分,信用卡号在传输过程中应加密,仅在将文件放置在客户端位置时解密
当 TenderId
为 PRIVATE_CC
时,信用卡号需要解密。我们使用如下所示的 XSLT 模板实现了这一点。如果 TenderId
为 PRIVATE_CC
,则下面的 XSLT 代码将源中除了 AccountNumber
之外的所有元素复制到目标。如果是 AccountNumber
,它会调用 C# 函数对其进行解密并将其存储在同一元素中。下面的代码应用于映射时,使用 C# 函数 fcnDecryptString
完成信用卡号的解密工作。
<xsl:template name="Copy" match="@* | node()">
<xsl:param name="param" />
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match=" CREDITDEBIT_TENDERLINEITEM[TenderId='PRIVATE_CC']">
<xsl:variable name="varEncryptedAcctNo" select="AccountNumber"/>
<xsl:element name="{name()}">
<xsl:for-each select="./*">
<xsl:choose>
<xsl:when test="name()='AccountNumber'">
<xsl:element name="AccountNumber">
<xsl:variable name="result" xmlns:ScriptS0="http://SPFunctionHelper"
select="ScriptS0:fcnDecryptString($varEncryptedAcctNo)" />
<xsl:value-of select="$result"/>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="."/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:element>
</xsl:template>
从 XSLT 调用外部程序集中的 C# 函数是使用 此博客 中建议的方法完成的。
处理 FF 中的可选元素,需要将销售架构中的所有 FF XSD 元素都设置为 Choice,minOccurs=0
和 maxoccurs
为 unbounded
我们收到了 FF XML 文件(基于 FF 架构),其中包含元素,或者有时会缺少某些元素。即使缺少某些元素,FF XML 架构也需要成功创建平面文件或将其解析为 XML。
为此,我们需要创建 Root
元素,并在根内部使用复杂类型,其中包含一个 minOccurs 0
和 maxOccurs unbounded
的 choice 元素,如下所示。这将支持 FILE_HEADER
或 FILE_TRANSLINEITEM
存在或缺失的场景,并且在这两种情况下,管道分隔的 FF 都将成功从 XML 生成或解析为 XML 文件。
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
…
<xs:element minOccurs="0" maxOccurs="unbounded" name="FILE_HEADER">
<xs:annotation>
<xs:appinfo>
<b:recordInfo structure="delimited"
child_delimiter_type="char" child_delimiter="|"
child_order="prefix" preserve_delimiter_for_empty_data="true"
suppress_trailing_delimiters="false" sequence_number="1"
tag_name="INSERT|TRANS_HEADER" />
</xs:appinfo>
</xs:annotation>
…
<xs:element minOccurs="0"
maxOccurs="unbounded" name="FILE_TRANSLINEITEM">
<xs:annotation>
<xs:appinfo>
<b:recordInfo structure="delimited" child_delimiter_type="char"
child_delimiter="|" child_order="prefix"
preserve_delimiter_for_empty_data="true"
suppress_trailing_delimiters="false"
sequence_number="2" tag_name="INSERT|TRANS_LINE_ITEM" />
</xs:appinfo>
</xs:annotation
…
FF 中的标头级别项目需要计算不同行级别值(例如金额、税金等)的总和或平均值
这已通过使用节点集概念完成,并为此提供了 xslt。如果订单有多个行项目,我们有一些要求计算行项目中所有金额的总和,并将它们存储在标头级别的字段中。
<xsl:variable name="tmpTotal">
<total_amount>
<xsl:for-each select="Item">
<itemtotal>
<xsl:value-of select="format-number(Item/Amount,'###,###,##0.00')" />
</itemtotal>
<xsl:variable name="varTotal" select="msxsl:node-set($tmpTotal)"/>
<xsl:variable name="varTotalofAllItems" xmlns:ScriptS0="http://SPFunctionHelper"
select="ScriptS0:RoundValue(sum($varTotal/total_amount/itemtotal)" />
上述 XSLT 创建了一个名为 tmpTotal
的变量,该变量维护所有行项目金额的单个值。它将单个金额存储在 itemtotal
变量中,该变量可以使用节点集变量 $varTotal
访问。表达式 sum($varTotal/total_amount/itemtotal)
计算所有行项目的总金额,并给出一个可以在标头级别使用的单个 Total
值。
FF 反汇编器和汇编器命令行工具已用于测试 FF 架构是否没有任何问题
BizTalk 提供了几个管道工具,可帮助我们测试 FF 或 XML 管道是否正确生成或解析文件。有关更多信息,请参阅 此链接。在我们的案例中,我们使用 FFAsm.exe 将 FF XML 架构转换为实际的平面文件。此外,还使用 FFDAsm.exe 将实际的管道分隔平面文件转换为 FF XML 架构。
它们位于 BizTalk 安装路径>\SDK\Utilities\PipelineTools。
FFAsm.exe file_inp.xml –bs myBodySchema.xsd
上述命令从 file_inp.xml 生成格式为 myBodySchema.xsd 的平面文件。
FFDasm.exe file_in.txt –bs myBodySchema.xsd
此命令从平面文件 file_in.txt 生成格式为 myBodySchema.xsd 的 XML 文件。
结论
希望本文能解决 BizTalk 开发中遇到的一些常见问题并提供解决方案。如有任何建议/意见,请随时提出。