转换示例

您可以使用 CEL 编写转换表达式,从而转换您的 CloudEvents 数据。如需了解详情,请参阅转换收到的事件

以下是一些常见用例和示例,展示了如何编写 CEL 表达式来转换事件数据。

标准用例

以下是转换事件数据的一些标准用例。

数据归一化

您需要扁平化事件消息中的嵌套数据结构,以便下游服务更轻松地进行处理。

场景:

假设您有以下 CloudEvents 数据:

{
  "data": {
    "orderId": "12345",
    "customer": {
      "firstName": "Alex",
      "lastName": "Taylor",
      "address": {
        "street": "1800 Amphibious Blvd.",
        "city": "Mountain View"
      }
    }
  }
}

您想编写一个 CEL 表达式,以生成以下输出:

{
  "data": {
    "orderId": "12345",
    "customerFirstName": "Alex",
    "customerLastName": "Taylor",
    "customerStreet": "1800 Amphibious Blvd.",
    "customerCity": "Mountain View"
  }
}
解决方案 1:

手动设置输出数据的格式。这样,您就可以列出字段名称,并仅选择输出中所需的元素。如果输入可预测且字段数量较少,则此方法是合理的。setField 函数会使用给定键添加或替换事件的字段。例如:

message.setField("data",
{
  "orderId": message.data.orderId,
  "customerFirstName": message.data.customer.firstName,
  "customerLastName": message.data.customer.lastName,
  "customerStreet": message.data.customer.address.street,
  "customerCity": message.data.customer.address.city,
})
解决方案 2:

在表达式中使用函数。setField 函数会使用给定键添加或替换事件的字段。denormalize 函数会将深层结构展平为键值对列表。字段名称使用英文句点 (.) 分隔,以便对结构层次结构进行细分。例如:

message.setField("data", message.data.denormalize())

这会产生以下与预期载荷略有不同的输出。不过,优点包括 CEL 表达式更短,可对任何输入进行操作,并且会自动包含任意数量的传入字段。

{
  "data": {
    "orderId": "12345",
    "customer.firstName": "Alex",
    "customer.lastName": "Taylor",
    "customer.address.street": "1800 Amphibious Blvd.",
    "customer.address.city": "Mountain View"
  }
}

数据遮盖

您需要在将事件载荷中的敏感数据发送到安全性较低的环境之前对其进行脱敏处理。

场景:

假设您有以下 CloudEvents 数据:

{
  "data": {
    "userId": "user123",
    "email": "alex@example.com",
    "creditCardNumber": "1234-5678-9012-3456"
  }
}

您想编写一个 CEL 表达式,以生成以下输出:

{
  "data": {
    "userId": "user123",
    "email": "a***@example.com",
    "creditCardNumber": "xxxx-xxxx-xxxx-3456"
  }
}
解决方案:

使用表达式遮盖任何敏感信息,例如电子邮件地址和信用卡号。setField 函数会使用给定键添加或替换事件的字段。extract 正则表达式函数遵循 RE2 语法。例如:

message
      .setField("data.email",
          re.extract(message.data.email,
                    "(^.).*@(.*)",
                    "\\1***@\\2"))

      .setField("data.creditCardNumber",
          re.extract(message.data.creditCardNumber,
                    "(\\d{4})\\D*$",
                    "xxxx-xxxx-xxxx-\\1"))

数据隐去

您需要根据特定条件从事件载荷中移除特定字段。

场景:

假设您有以下 CloudEvents 数据:

{
  "data": {
    "orderId": "12345",
    "customerType": "gold",
    "discountCode": "VIP"
  }
}

您想编写一个 CEL 表达式,以生成以下输出:

{
  {
  "orderId": "12345",
  "customerType": "gold"
  }
}
解决方案:

使用一个表达式,在 customerType 为“gold”时隐去 discountCode 字段。removeFields 函数可从事件中移除特定字段。例如:

message.data.customerType == "gold" ?
      message.removeFields(["data.discountCode"]) :
      message

数据转换

您需要将数据从一种格式或类型转换为另一种格式或类型。

场景:

假设您有以下 CloudEvents 数据:

{
  "data": {
    "orderDate": "2024-10-31T12:00:00Z",
    "totalAmount": "1500"
  }
}

您想编写一个 CEL 表达式,以生成以下输出:

{
  "data": {
    "orderDate": 1704086400,
    "totalAmount": 1500.00
  }
}
解决方案:

使用将 orderDate 转换为 UNIX 时间戳,以及将 totalAmount 类型从 string 转换为 double(浮点数)的表达式。setField 函数会使用给定键添加或替换事件的字段。您可以使用字符串处理函数来转换字符串结果。例如:

message
      .setField("data.orderDate", int(timestamp(message.data.orderDate)))
      .setField("data.totalAmount", double(message.data.totalAmount))

有条件的路由

您需要根据事件数据将事件路由到不同的目标位置。

场景:

假设您有以下 CloudEvents 数据:

{
  "data": {
    "eventType": "order.created",
    "orderValue": 200
  }
}

您想编写一个 CEL 表达式,以生成以下输出:

{
  "data": {
    "eventType": "order.created",
    "orderValue": 200,
    "routingKey": "highValue"
  }
}
解决方案:

使用一个表达式,如果 orderValue 大于 100,则添加一个值为“highValue”的 routingKey 字段;否则,添加值为 "normal"routingKey 字段。routingKey 字段可用于确定路由路径。setField 函数会使用给定键添加或替换事件的字段。例如:

message.data.orderValue > 100 ?
      message.setField("data.routingKey", "highValue") :
      message.setField("data.routingKey", "normal")

默认值处理

您需要确保事件载荷中的某些字段具有默认值(如果没有)。

场景:

假设您有以下 CloudEvents 数据:

{
  "data": {
    "itemName": "Product A"
  }
}

您想编写一个 CEL 表达式,以生成以下输出:

{
  "data": {
    "itemName": "Product A",
    "quantity": 1
  }
}
解决方案:

使用一个表达式,在 quantity 字段不存在时添加一个默认值为 1quantity 字段。has 宏用于测试某个字段是否可用。setField 函数会使用给定键添加或替换事件的字段。例如:

has(message.data.quantity)  ?
    message :
    message.setField("data.quantity", 1)

字符串操作

您需要提取或修改事件数据中字符串字段的部分内容。

场景:

假设您有以下 CloudEvents 数据:

{
  "data": {
    "customerEmail": "alex@example.com"
  }
}

您想编写一个 CEL 表达式,以生成以下输出:

{
  "data": {
    "customerEmail": "alex@example.com",
    "emailDomain": "example.com"
  }
}
解决方案:

使用一个表达式从 customerEmail 字段中提取域名(“example.com”)并将其存储在新 emailDomain 字段中。setField 函数会使用给定键添加或替换事件的字段。extract 正则表达式函数遵循 RE2 语法。例如:

message
  .setField("data.emailDomain",
re.extract(message.data.customerEmail, "(^.*@)(.*)", "\\2"))

列表和映射操作

您需要处理事件数据中的列表或映射。

场景:

假设您有以下 CloudEvents 数据:

{
  "data": {
    "productIds": [
      "product123",
      "product456"
    ]
  }
}

您想编写一个 CEL 表达式,以生成以下输出:

{
  "data": {
    "productIds": [
      "product123",
      "product456"
    ],
    "productFound": true
  }
}
解决方案:

使用一个表达式检查 productIds 列表中是否存在“product456”,并将结果(truefalse)存储在新 productFound 字段中。setField 函数会使用给定键添加或替换事件的字段。exists 宏用于测试谓词是否适用于列表中的所有元素,并使用“或”运算符组合结果。例如:

message.setField("data.productFound",
        message.data.productIds.exists(id, id == "product123"))

错误处理

您需要妥善处理事件载荷中的潜在错误或意外数据。

场景:

假设您有以下 CloudEvents 数据:

{
  "data": {
    "quantity": "abc"
  }
}

您想编写一个 CEL 表达式,以生成以下输出:

{
  "data": {
    "quantity": 0,
    "error": "Invalid quantity"
  }
}
解决方案:

使用尝试将 quantity 字段转换为整数的表达式。如果转换失败,请将 quantity 字段设置为 0,然后添加一个值为“数量无效”的新 error 字段。

  • has 宏用于测试字段是否可用。
  • type 函数会返回值的类型。
  • matches 正则表达式函数遵循 RE2 语法
  • setField 函数用于添加或替换事件的字段(使用给定键)。

例如:

// Check if data.quantity exists
has(message.data.quantity) &&
// Check if data.quantity is a string
type(message.data.quantity) == string &&
// Check if string consists of digits
message.data.quantity.matches(r'^-?[0-9]+$') ?
  // If data.quantity is valid, use message
  message :
  // If data.quantity is invalid, set to 0 and generate error
  message
    .setField("data.quantity", 0)
    .setField("data.error", "Invalid quantity")

复杂用例

以下是转换事件数据的一些复杂用例。

数据转换

您需要对嵌套事件数据执行多个转换。

场景:

假设您有以下 CloudEvents 数据:

{
  "data": {
    "orderId": "12345",
    "customer": {
      "firstName": "Alex",
      "lastName": "Taylor",
      "email": "alex@example.com",
      "address": {
        "street": "1800 Amphibious Blvd.",
        "city": "Mountain View",
        "state": "CA"
      }
    },
    "items": [
      {
        "itemId": "item1",
        "price": 10.00,
        "quantity": 2
      },
      {
        "itemId": "item2",
        "price": 5.00,
        "quantity": 1
      }
    ]
  }
}

您想编写一个 CEL 表达式,以生成以下输出:

{
  "data": {
    "orderId": "12345",
    "customer.firstName": "Alex",
    "customer.lastName": "Taylor",
    "customer.email": "a***@example.com",
    "customer.address.city": "Mountain View",
    "customer.address.state": "CA"
  }
}
解决方案:

使用一个表达式,从地址中提取城市和州,并屏蔽电子邮件地址。

  • setField 函数用于添加或替换事件的字段(使用给定键)。
  • toMap 函数可将 CEL 映射的 CEL 列表转换为单个 CEL 映射。
  • extract 正则表达式函数遵循 RE2 语法
  • removeFields 函数用于从事件中移除特定字段。
  • denormalize 函数会将深层结构展平为键值对列表。字段名称使用英文句号 (.) 分隔,以便对结构层次结构进行细分。

例如:

message
.setField("data",
  message.data.setField("customer.address",
    message.data.customer.address.map(key, key == "city" || key == "state",
          { key: message.data.customer.address[key] }).toMap())
  .setField("customer.email",
        re.extract(message.data.customer.email, "(^..?).*@(.*)", "\\1***@\\2"))
  .removeFields(["items"])
  .denormalize()
)

数据格式设置和路由

您需要设置事件数据格式、添加商品信息,然后路由事件消息。

场景:

假设您有以下 CloudEvents 数据:

{
  "data": {
    "productId": "p123",
    "productName": "Example Product",
    "category": "electronics"
  }
}

您想编写一个 CEL 表达式,以生成以下输出:

{
  "data": {
    "productId": "electronics-p123",
    "productName": "EXAMPLE PRODUCT",
    "category": "electronics",
    "routingKey": "electronics"
  }
}
解决方案:

使用一个表达式将商品名称设置为大写格式,根据商品类别为商品 ID 添加前缀,并包含用于下游处理的路由键。setField 函数会使用给定键添加或替换事件的字段。upperAscii 函数会返回一个字符串,其中所有 ASCII 字符均已转换为对应的大写字符。例如:

message
.setField("data.productId",
message.data.category + "-" + message.data.productId)
.setField("data.productName", message.data.productName.upperAscii())
.setField("data.routingKey", message.data.category)