You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SAP CPI中Multipart/mixed消息Groovy脚本处理需求咨询

SAP CPI处理Multipart/mixed Payload的字段替换方案

我来给你详细说说在SAP CPI里处理这个嵌套Multipart/mixed payload的具体方案,分脚本实现思路和CPI操作配置两步来讲:

Groovy脚本实现思路

你的payload是两层嵌套的Multipart结构(外层batch,内层changeset),核心思路是逐层解析、修改JSON内容再重新组装,具体步骤如下:

1. 解析嵌套的Multipart结构

先用Apache的MultipartStream工具逐层拆解payload:先处理外层的--batch边界,遍历每个子part;遇到内层changeset的Multipart部分时,再拆解内层的每个HTTP请求part。

2. 提取HTTP请求中的JSON Body

每个changeset里的内容是application/http类型的PATCH请求,我们通过分割HTTP头和Body(找两个连续的换行符),把JSON部分单独提取出来,转成Groovy的Map对象方便修改。

3. 字段替换与移除

遍历每个JSON Map:如果同时存在PurReqnExternalSystemType和PurchasingDocumentCategory字段,就把前者的值替换为后者,然后删除PurchasingDocumentCategory字段。

4. 重新组装Multipart内容

把修改后的JSON替换回原来的HTTP请求部分,再按照原始的Multipart格式,用正确的边界符重新拼接所有part,确保输出的payload格式符合要求。

完整Groovy脚本示例

import org.apache.commons.fileupload.MultipartStream
import groovy.json.JsonSlurper
import groovy.json.JsonBuilder

def processMultipartPayload(String payload, String outerBoundary, String innerBoundary) {
    def result = new StringBuilder()
    def inputStream = new ByteArrayInputStream(payload.getBytes())
    
    // 处理外层batch multipart
    def outerMultipart = new MultipartStream(inputStream, outerBoundary.getBytes(), 8192, null)
    boolean hasNextOuter = outerMultipart.skipPreamble()
    
    while (hasNextOuter) {
        def headers = outerMultipart.readHeaders()
        def partContent = new ByteArrayOutputStream()
        outerMultipart.readBodyData(partContent)
        def partStr = partContent.toString()
        
        // 判断是否是内层changeset的multipart
        if (headers.toLowerCase().contains('content-type: multipart/mixed; boundary=' + innerBoundary)) {
            def innerResult = new StringBuilder()
            def innerInputStream = new ByteArrayInputStream(partStr.getBytes())
            def innerMultipart = new MultipartStream(innerInputStream, innerBoundary.getBytes(), 8192, null)
            boolean hasNextInner = innerMultipart.skipPreamble()
            
            while (hasNextInner) {
                def innerHeaders = innerMultipart.readHeaders()
                def innerPartContent = new ByteArrayOutputStream()
                innerMultipart.readBodyData(innerPartContent)
                def innerPartStr = innerPartContent.toString()
                
                // 处理application/http类型的part(包含PATCH请求)
                if (innerHeaders.toLowerCase().contains('content-type: application/http')) {
                    // 分割HTTP头和JSON body(两个连续换行)
                    def parts = innerPartStr.split('\r\n\r\n', 2)
                    if (parts.length == 2) {
                        def httpHeader = parts[0]
                        def jsonStr = parts[1]
                        def jsonSlurper = new JsonSlurper()
                        def jsonMap = jsonSlurper.parseText(jsonStr)
                        
                        // 替换字段并移除目标字段
                        if (jsonMap.PurReqnExternalSystemType != null && jsonMap.PurchasingDocumentCategory != null) {
                            jsonMap.PurReqnExternalSystemType = jsonMap.PurchasingDocumentCategory
                            jsonMap.remove('PurchasingDocumentCategory')
                        }
                        
                        // 重新生成JSON字符串
                        def newJsonStr = new JsonBuilder(jsonMap).toPrettyString()
                        // 拼接回HTTP请求内容
                        innerPartStr = "${httpHeader}\r\n\r\n${newJsonStr}"
                    }
                }
                
                // 添加到内层结果集
                innerResult.append("--${innerBoundary}\r\n")
                innerResult.append(innerHeaders)
                innerResult.append("\r\n")
                innerResult.append(innerPartStr)
                innerResult.append("\r\n")
                
                hasNextInner = innerMultipart.readBoundary()
            }
            
            // 添加内层Multipart结束符
            innerResult.append("--${innerBoundary}--\r\n")
            partStr = innerResult.toString()
        }
        
        // 添加到外层结果集
        result.append("--${outerBoundary}\r\n")
        result.append(headers)
        result.append("\r\n")
        result.append(partStr)
        result.append("\r\n")
        
        hasNextOuter = outerMultipart.readBoundary()
    }
    
    // 添加外层Multipart结束符
    result.append("--${outerBoundary}--")
    return result.toString()
}

// 获取原始payload和边界符(推荐从Content-Type头解析,这里简化演示)
def payload = message.getBody(String)
def contentType = message.getHeaders().get('Content-Type', '')
def outerBoundary = contentType =~ /boundary=(.*)/ ? (contentType =~ /boundary=(.*)/)[0][1] : 'batch'
def innerBoundary = 'changeset' // 也可以从内层Content-Type头解析,这里硬编码简化

// 处理payload并设置回消息体
def processedPayload = processMultipartPayload(payload, outerBoundary, innerBoundary)
message.setBody(processedPayload)

SAP CPI操作建议

1. 脚本放置位置

在你的Integration Flow中,把这个Groovy Script步骤放在Receive Step之后、任何其他映射/转换步骤之前,确保原始payload未被修改时就进行处理。

2. 边界符解析优化

脚本里已经包含了从Content-Type头解析外层边界符的逻辑,不要硬编码边界符(除非你能100%确定边界符不会变),这样能适配不同场景的Multipart请求。

3. 测试验证

  • 先在本地用Groovy Console测试脚本:抓取CPI里的原始payload,复制到本地编辑器运行脚本,检查修改后的JSON是否符合要求。
  • 部署到CPI后,用Message Monitor查看处理前后的payload对比,确保格式和字段修改都正确。

4. 头信息维护

处理完payload后,确保Content-Type头保持不变(依然是multipart/mixed; boundary=batch),避免下游系统解析出错。如果头信息被修改,可以在脚本末尾添加:

message.setHeader('Content-Type', "multipart/mixed; boundary=${outerBoundary}")

内容的提问来源于stack exchange,提问作者user928700

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 08:05:52