使用逻辑编排解决阿里云费用账单API集成数据结构差异的问题

集成阿里云的费用账单,是公司运维小伙伴经常要承担的工作。阿里云也开放了按照多个维维度查询费用账单的API供企业客户使用,集成到云管平台,财务系统等等。不过在实际工作中,常常会发现需要集成的云供应商不止一家,每一家提供的API格式都不一样。如果想要接入已经开发好的系统,数据结构不一致就需要做额外的数据Mapping工作,费时费力。小编在阿里云上发现了一款神器,可对阿里云提供的API做快速的定制,输出想要的格式,这就是阿里云的逻辑编排。

什么是逻辑编排?

举个实际的例子,阿里云账单的API QueryInstanceBill可返回按照实例维度或者计费项维度的账单,返回的格式是这样的:
"Data": {

"Items": {
  "Item": [
    {
                "SubscriptionType": "PayAsYouGo",
                "ProductCode": "cdn",
                "RecordID": "2019080352963162",
                "BillingItem": "InlandNetworkOut",
                "ProductDetail": "cdn",
                "DeductedByPrepaidCard": 0,
                "PretaxAmount": 0,
                "DeductedByCoupons": 0,
                "RoundDownDiscount": 0,
                "UsageStartTime": "2019-08-08 12:00:00",
                "UsageEndTime": "2019-08-08 13:00:00",
                "Status": "NoSettle",
                "PaymentTime": "",
                "PaymentAmount": 0,
                "Item": "PayAsYouGoBill",
                "OutstandingAmount": 0,
                "ProductType": "",
                "DeductedByCashCoupons": 0,
                "OwnerID": "",
                "ProductName": "cdn",
                "Currency": "CNY",
                "PretaxGrossAmount": 0,
                "InvoiceDiscount": 0
            }
            ]

},

如果在集成的过程中,仅希望InlandNetworkOut这一个计费项的用量和金额汇总,输出一个特定Json格式的结果,可以按照下面的步骤操作。

第一步 权限设置

逻辑编排过程中需要调用QueryInstanceBill接口,因此需要相关的权限。可以使用AliyunBSSFullAccess权限,但为了保证最小可用原则,创建一个新的权限策略AliyunBSSBillReadOnly,使用以下的Statement
{
"Version": "1",
"Statement": [

   {
       "Action": ["bss:Query*", "bss:Describe*"],
       "Resource": "*",
       "Effect": "Allow"
   }

]
}
使用逻辑编排解决阿里云费用账单API集成数据结构差异的问题
同时创建一个拥有此权限Ram角色,并添加以下的信任策略:

  1. 创建RAM Role,选择Alibaba Cloud Service类型
  2. 添加刚刚创建的策略AliyunBSSBillReadOnly
  3. 修改信任策略,让逻辑编排可以访问
    {

    "Statement": [
        {
            "Action": "sts:AssumeRole",
            "Effect": "Allow",
            "Principal": {
                "Service": [
                    "composer.aliyuncs.com"
                ]
            }
        }
    ],
    "Version": "1"

    }

使用逻辑编排解决阿里云费用账单API集成数据结构差异的问题

第二步 编排流程
登录国际站并访问逻辑编排控制台https://lc.console.aliyun.com/,在左侧的菜单选择“我的工作流”,然后创建一个新的流程,输入名称和描述。创建完成后,转到“代码设计”,然后复制本文给出的一个段脚本。
{

"actions": {
    "查询_InstanceBill_第一页数据": {
        "type": "ACS::BSS::QueryInstanceBill",
        "inputs": {
            "method": "POST",
            "host": {
                "product": "BSS",
                "api": "QueryInstanceBill",
                "apiVersion": "2017-12-14"
            },
            "connection": {},
            "queries": {
                "RegionId": "ap-southeast-1",
                "BillingCycle": "@get(triggerOutputs(), 'queries.BillingCycle')",
                "ProductCode": "cdn",
                "PageNum": 1,
                "PageSize": 300,
                "IsBillingItem": true
            }
        },
        "runAfter": {
            "初始化变量存放所有记录": [
                "Succeeded"
            ]
        }
    },
    "初始化变量存放所有记录": {
        "type": "InitializeVariable",
        "inputs": {
            "name": "records",
            "type": "Array",
            "value": []
        },
        "runAfter": {}
    },
    "存入第一页数据": {
        "type": "AppendToArrayVariable",
        "inputs": {
            "name": "records",
            "value": "@body('查询_InstanceBill_第一页数据')['Data']['Items']['Item']"
        },
        "runAfter": {
            "查询_InstanceBill_第一页数据": [
                "Succeeded"
            ]
        }
    },
    "遍历剩余页数": {
        "type": "Until",
        "inputs": {},
        "runAfter": {
            "初始化变量存放当前页数": [
                "Succeeded"
            ]
        },
        "actions": {
            "查询某一页_InstanceBill_数据": {
                "type": "ACS::BSS::QueryInstanceBill",
                "inputs": {
                    "method": "POST",
                    "host": {
                        "product": "BSS",
                        "api": "QueryInstanceBill",
                        "apiVersion": "2017-12-14"
                    },
                    "connection": {},
                    "queries": {
                        "BillingCycle": "@get(triggerOutputs(), 'queries.BillingCycle')",
                        "RegionId": "ap-southeast-1",
                        "ProductCode": "cdn",
                        "PageNum": "@variables('pageNumber')",
                        "PageSize": 300,
                        "IsBillingItem": true
                    }
                },
                "runAfter": {
                    "设置页数": [
                        "Succeeded"
                    ]
                }
            },
            "追加值至数组变量": {
                "type": "AppendToArrayVariable",
                "inputs": {
                    "name": "records",
                    "value": "@body('查询某一页_InstanceBill_数据')['Data']['Items']['Item']"
                },
                "runAfter": {
                    "查询某一页_InstanceBill_数据": [
                        "Succeeded"
                    ]
                }
            },
            "设置页数": {
                "runAfter": {},
                "type": "SetVariable",
                "inputs": {
                    "name": "pageNumber",
                    "value": "@add(variables('pageNumber'), 1)"
                }
            }
        },
        "expression": "@lte(sub(ceil(div(body('查询_InstanceBill_第一页数据').Data.TotalCount, 300)), variables('pageNumber')), 0)"
    },
    "初始化变量存放当前页数": {
        "type": "InitializeVariable",
        "inputs": {
            "name": "pageNumber",
            "type": "Integer",
            "value": 1
        },
        "runAfter": {
            "存入第一页数据": [
                "Succeeded"
            ]
        }
    },
    "计算总数": {
        "type": "JavascriptCode",
        "inputs": {
            "code": "const records = $context.variables('records');\n\nconst allUsage = records.reduce((sum, group) => {\n    if (Array.isArray(group)) {\n        group.forEach((record) => {\n            if (record.BillingItem === 'InlandNetworkOut') {\n                if (record.Usage) {\n                    sum.Usage += parseFloat(record.Usage) || 0;\n                }\n                if (record.PretaxAmount) {\n                    sum.PretaxAmount += parseFloat(record.PretaxAmount) || 0;\n                }\n            }\n            \n        });\n    } else if (group.Usage || group.PretaxAmount) {\n        if (group.BillingItem === 'InlandNetworkOut') {\n            sum.Usage += parseFloat(group.Usage) || 0;\n            sum.PretaxAmount += parseFloat(group.PretaxAmount) || 0;\n        }\n    }\n    \n    return sum;\n}, { Usage: 0, PretaxAmount: 0 });\n\nreturn allUsage;"
        },
        "runAfter": {
            "遍历剩余页数": [
                "Succeeded"
            ]
        }
    },
    "响应": {
        "type": "Response",
        "outputs": {
            "statusCode": 500,
            "body": {                  
                "cost_data": [
                    {                        
                        "usage": "@body('计算总数').Usage",
                        "cost": "@body('计算总数').PretaxAmount"
                    }
                ]
            },
            "headers": {
                "Content-Type": "application/json"
            }
        },
        "runAfter": {
            "计算总数": [
                "Succeeded"
            ]
        }
    }
}

}

返回图形化设置,这时需要对其中调用API的过程进行授权。点击并展开“查询InstanceBill第一页数据”,在下方点击Change Permissions,添加刚刚创建新的RAM Role,注意Authorized Policies是否包括了应该有的权限。再点开“遍历剩余页数”这一步,再展开“查询某一页InstanceBill数据”,进行如上的权限添加步骤。点击右上角的保存按键保存流程。

第三步 调用流程
在流程的第一步中,可以找到服务发起的URL,Copy到浏览器中可进行测试。在上面的例子中,调用时需要添加一个查询账期的参数BillingCycle=YYYY-MM。根据所需,可更改调用方式为POST或者GET。

这就是一个利用流程编改变API结构的方式,希望能够帮助到您。

上一篇:业务安全通用解决方案--WAF数据风控


下一篇:DevOps:软件架构师行动指南3.1 概述