pixelarraylib 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- arraylib/__init__.py +36 -0
- arraylib/__main__.py +126 -0
- arraylib/aliyun/__init__.py +0 -0
- arraylib/aliyun/aliyun_email.py +130 -0
- arraylib/aliyun/billing.py +477 -0
- arraylib/aliyun/content_scanner.py +253 -0
- arraylib/aliyun/domain.py +434 -0
- arraylib/aliyun/eci.py +47 -0
- arraylib/aliyun/ecs.py +68 -0
- arraylib/aliyun/fc.py +142 -0
- arraylib/aliyun/oss.py +649 -0
- arraylib/aliyun/sms.py +59 -0
- arraylib/aliyun/sts.py +124 -0
- arraylib/db_utils/mysql.py +544 -0
- arraylib/db_utils/redis.py +373 -0
- arraylib/decorators/__init__.py +13 -0
- arraylib/decorators/decorators.py +194 -0
- arraylib/gitlab/__init__.py +0 -0
- arraylib/gitlab/code_analyzer.py +344 -0
- arraylib/gitlab/pypi_package_manager.py +61 -0
- arraylib/monitor/__init__.py +0 -0
- arraylib/monitor/feishu.py +132 -0
- arraylib/net/request.py +143 -0
- arraylib/scripts/__init__.py +22 -0
- arraylib/scripts/collect_code_to_txt.py +327 -0
- arraylib/scripts/create_test_case_files.py +100 -0
- arraylib/scripts/nginx_proxy_to_ecs.py +119 -0
- arraylib/scripts/remove_empty_lines.py +120 -0
- arraylib/scripts/summary_code_count.py +430 -0
- arraylib/system/__init__.py +0 -0
- arraylib/system/common.py +390 -0
- pixelarraylib-1.0.0.dist-info/METADATA +141 -0
- pixelarraylib-1.0.0.dist-info/RECORD +37 -0
- pixelarraylib-1.0.0.dist-info/WHEEL +5 -0
- pixelarraylib-1.0.0.dist-info/entry_points.txt +2 -0
- pixelarraylib-1.0.0.dist-info/licenses/LICENSE +21 -0
- pixelarraylib-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import traceback
|
|
3
|
+
import pandas as pd
|
|
4
|
+
from alibabacloud_bssopenapi20171214.client import Client as BssOpenApi20171214Client
|
|
5
|
+
from alibabacloud_tea_openapi import models as open_api_models
|
|
6
|
+
from alibabacloud_bssopenapi20171214 import models as bss_open_api_20171214_models
|
|
7
|
+
from alibabacloud_tea_util import models as util_models
|
|
8
|
+
from arraylib.monitor.feishu import Feishu
|
|
9
|
+
|
|
10
|
+
feishu_alert = Feishu("devtoolkit服务报警")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class BillingUtils:
|
|
14
|
+
def __init__(
|
|
15
|
+
self, access_key_id, access_key_secret, use_proxy=False, proxy_url=None
|
|
16
|
+
):
|
|
17
|
+
"""
|
|
18
|
+
description:
|
|
19
|
+
初始化阿里云计费工具类
|
|
20
|
+
parameters:
|
|
21
|
+
access_key_id(str): 阿里云访问密钥ID
|
|
22
|
+
access_key_secret(str): 阿里云访问密钥Secret
|
|
23
|
+
use_proxy(bool): 是否使用代理,默认False
|
|
24
|
+
proxy_url(str): 代理URL,格式如 http://127.0.0.1:7897
|
|
25
|
+
"""
|
|
26
|
+
config = open_api_models.Config(
|
|
27
|
+
access_key_id=access_key_id,
|
|
28
|
+
access_key_secret=access_key_secret,
|
|
29
|
+
endpoint="business.aliyuncs.com",
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
self.client = BssOpenApi20171214Client(config)
|
|
33
|
+
|
|
34
|
+
def _format_bill_response(self, response, with_comments=False):
|
|
35
|
+
"""
|
|
36
|
+
description:
|
|
37
|
+
格式化账单查询响应为JSON格式
|
|
38
|
+
|
|
39
|
+
parameters:
|
|
40
|
+
response(dict): API响应对象
|
|
41
|
+
with_comments(bool): 是否添加注释,默认False
|
|
42
|
+
return:
|
|
43
|
+
dict: 格式化后的响应数据
|
|
44
|
+
"""
|
|
45
|
+
# 字段注释映射
|
|
46
|
+
field_comments = {
|
|
47
|
+
"Message": "错误信息",
|
|
48
|
+
"RequestId": "请求ID",
|
|
49
|
+
"BillingCycle": "账期",
|
|
50
|
+
"TotalCount": "总记录数",
|
|
51
|
+
"AccountID": "账号ID",
|
|
52
|
+
"AccountName": "账号名称",
|
|
53
|
+
"MaxResults": "本次请求所返回的最大记录数",
|
|
54
|
+
"NextToken": "用来表示当前调用返回读取到的位置,空代表数据已经读取完毕",
|
|
55
|
+
"Code": "状态码",
|
|
56
|
+
"Success": "是否成功",
|
|
57
|
+
"ProductName": "产品名称",
|
|
58
|
+
"SubOrderId": "该条账单对应的订单明细ID",
|
|
59
|
+
"BillAccountID": "账单所属账号ID",
|
|
60
|
+
"DeductedByCashCoupons": "代金券折扣",
|
|
61
|
+
"PaymentTime": "订单支付时间",
|
|
62
|
+
"PaymentAmount": "现金支付(包含信用额度退款抵扣)",
|
|
63
|
+
"DeductedByPrepaidCard": "储值卡抵扣",
|
|
64
|
+
"InvoiceDiscount": "优惠金额",
|
|
65
|
+
"UsageEndTime": "账单结束时间",
|
|
66
|
+
"Item": "账单类型",
|
|
67
|
+
"SubscriptionType": "订阅类型",
|
|
68
|
+
"PretaxGrossAmount": "原始金额",
|
|
69
|
+
"Currency": "货币类型",
|
|
70
|
+
"CommodityCode": "商品Code,与费用中心产品明细Code一致",
|
|
71
|
+
"UsageStartTime": "账单开始时间",
|
|
72
|
+
"AdjustAmount": "信用额度退款抵扣",
|
|
73
|
+
"Status": "支付状态",
|
|
74
|
+
"DeductedByCoupons": "优惠券抵扣",
|
|
75
|
+
"RoundDownDiscount": "抹零优惠",
|
|
76
|
+
"ProductDetail": "产品明细",
|
|
77
|
+
"ProductCode": "产品代码",
|
|
78
|
+
"ProductType": "产品类型",
|
|
79
|
+
"OutstandingAmount": "未结清金额",
|
|
80
|
+
"BizType": "业务类型",
|
|
81
|
+
"PipCode": "产品Code,与费用中心账单产品Code一致",
|
|
82
|
+
"PretaxAmount": "应付金额",
|
|
83
|
+
"OwnerID": "自帐号AccountID(多账号代付场景)",
|
|
84
|
+
"BillAccountName": "账单所属账号名称",
|
|
85
|
+
"RecordID": "订单号、账单号",
|
|
86
|
+
"CashAmount": "现金支付(不包含信用额度退款抵扣)",
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
def add_comment(value, field_name):
|
|
90
|
+
"""为字段值添加注释"""
|
|
91
|
+
if with_comments and field_name in field_comments:
|
|
92
|
+
return f"{value} # {field_comments[field_name]}"
|
|
93
|
+
return value
|
|
94
|
+
|
|
95
|
+
try:
|
|
96
|
+
# 构建基础响应结构
|
|
97
|
+
formatted_response = {
|
|
98
|
+
"Message": add_comment(
|
|
99
|
+
"Successful!" if response.body.success else "Failed", "Message"
|
|
100
|
+
),
|
|
101
|
+
"RequestId": add_comment(response.body.request_id, "RequestId"),
|
|
102
|
+
"Data": {
|
|
103
|
+
"BillingCycle": add_comment(
|
|
104
|
+
response.body.data.billing_cycle, "BillingCycle"
|
|
105
|
+
),
|
|
106
|
+
"TotalCount": add_comment(
|
|
107
|
+
response.body.data.total_count, "TotalCount"
|
|
108
|
+
),
|
|
109
|
+
"AccountID": add_comment(
|
|
110
|
+
getattr(response.body.data, "account_id", ""), "AccountID"
|
|
111
|
+
),
|
|
112
|
+
"AccountName": add_comment(
|
|
113
|
+
getattr(response.body.data, "account_name", ""), "AccountName"
|
|
114
|
+
),
|
|
115
|
+
"MaxResults": add_comment(
|
|
116
|
+
getattr(response.body.data, "max_results", 0), "MaxResults"
|
|
117
|
+
),
|
|
118
|
+
"Items": {"Item": []},
|
|
119
|
+
},
|
|
120
|
+
"Code": add_comment(
|
|
121
|
+
"Success" if response.body.success else "Failed", "Code"
|
|
122
|
+
),
|
|
123
|
+
"Success": add_comment(response.body.success, "Success"),
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
# 添加NextToken(如果存在)
|
|
127
|
+
if (
|
|
128
|
+
hasattr(response.body.data, "next_token")
|
|
129
|
+
and response.body.data.next_token
|
|
130
|
+
):
|
|
131
|
+
formatted_response["Data"]["NextToken"] = add_comment(
|
|
132
|
+
response.body.data.next_token, "NextToken"
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
# 处理账单项
|
|
136
|
+
if hasattr(response.body.data, "items") and response.body.data.items:
|
|
137
|
+
items = response.body.data.items
|
|
138
|
+
if hasattr(items, "item") and items.item:
|
|
139
|
+
bill_items = items.item
|
|
140
|
+
if not isinstance(bill_items, list):
|
|
141
|
+
bill_items = [bill_items]
|
|
142
|
+
|
|
143
|
+
for item in bill_items:
|
|
144
|
+
item_data = {
|
|
145
|
+
"ProductName": add_comment(
|
|
146
|
+
getattr(item, "product_name", ""), "ProductName"
|
|
147
|
+
),
|
|
148
|
+
"SubOrderId": add_comment(
|
|
149
|
+
getattr(item, "sub_order_id", ""), "SubOrderId"
|
|
150
|
+
),
|
|
151
|
+
"BillAccountID": add_comment(
|
|
152
|
+
getattr(item, "bill_account_id", ""), "BillAccountID"
|
|
153
|
+
),
|
|
154
|
+
"DeductedByCashCoupons": add_comment(
|
|
155
|
+
getattr(item, "deducted_by_cash_coupons", 0),
|
|
156
|
+
"DeductedByCashCoupons",
|
|
157
|
+
),
|
|
158
|
+
"PaymentTime": add_comment(
|
|
159
|
+
getattr(item, "payment_time", ""), "PaymentTime"
|
|
160
|
+
),
|
|
161
|
+
"PaymentAmount": add_comment(
|
|
162
|
+
getattr(item, "payment_amount", 0), "PaymentAmount"
|
|
163
|
+
),
|
|
164
|
+
"DeductedByPrepaidCard": add_comment(
|
|
165
|
+
getattr(item, "deducted_by_prepaid_card", 0),
|
|
166
|
+
"DeductedByPrepaidCard",
|
|
167
|
+
),
|
|
168
|
+
"InvoiceDiscount": add_comment(
|
|
169
|
+
getattr(item, "invoice_discount", 0), "InvoiceDiscount"
|
|
170
|
+
),
|
|
171
|
+
"UsageEndTime": add_comment(
|
|
172
|
+
getattr(item, "usage_end_time", ""), "UsageEndTime"
|
|
173
|
+
),
|
|
174
|
+
"Item": add_comment(getattr(item, "item", ""), "Item"),
|
|
175
|
+
"SubscriptionType": add_comment(
|
|
176
|
+
getattr(item, "subscription_type", ""),
|
|
177
|
+
"SubscriptionType",
|
|
178
|
+
),
|
|
179
|
+
"PretaxGrossAmount": add_comment(
|
|
180
|
+
getattr(item, "pretax_gross_amount", 0),
|
|
181
|
+
"PretaxGrossAmount",
|
|
182
|
+
),
|
|
183
|
+
"Currency": add_comment(
|
|
184
|
+
getattr(item, "currency", "CNY"), "Currency"
|
|
185
|
+
),
|
|
186
|
+
"CommodityCode": add_comment(
|
|
187
|
+
getattr(item, "commodity_code", ""), "CommodityCode"
|
|
188
|
+
),
|
|
189
|
+
"UsageStartTime": add_comment(
|
|
190
|
+
getattr(item, "usage_start_time", ""), "UsageStartTime"
|
|
191
|
+
),
|
|
192
|
+
"AdjustAmount": add_comment(
|
|
193
|
+
getattr(item, "adjust_amount", 0), "AdjustAmount"
|
|
194
|
+
),
|
|
195
|
+
"Status": add_comment(
|
|
196
|
+
getattr(item, "status", ""), "Status"
|
|
197
|
+
),
|
|
198
|
+
"DeductedByCoupons": add_comment(
|
|
199
|
+
getattr(item, "deducted_by_coupons", 0),
|
|
200
|
+
"DeductedByCoupons",
|
|
201
|
+
),
|
|
202
|
+
"RoundDownDiscount": add_comment(
|
|
203
|
+
getattr(item, "round_down_discount", 0),
|
|
204
|
+
"RoundDownDiscount",
|
|
205
|
+
),
|
|
206
|
+
"ProductDetail": add_comment(
|
|
207
|
+
getattr(item, "product_detail", ""), "ProductDetail"
|
|
208
|
+
),
|
|
209
|
+
"ProductCode": add_comment(
|
|
210
|
+
getattr(item, "product_code", ""), "ProductCode"
|
|
211
|
+
),
|
|
212
|
+
"ProductType": add_comment(
|
|
213
|
+
getattr(item, "product_type", ""), "ProductType"
|
|
214
|
+
),
|
|
215
|
+
"OutstandingAmount": add_comment(
|
|
216
|
+
getattr(item, "outstanding_amount", 0),
|
|
217
|
+
"OutstandingAmount",
|
|
218
|
+
),
|
|
219
|
+
"BizType": add_comment(
|
|
220
|
+
getattr(item, "biz_type", ""), "BizType"
|
|
221
|
+
),
|
|
222
|
+
"PipCode": add_comment(
|
|
223
|
+
getattr(item, "pip_code", ""), "PipCode"
|
|
224
|
+
),
|
|
225
|
+
"PretaxAmount": add_comment(
|
|
226
|
+
getattr(item, "pretax_amount", 0), "PretaxAmount"
|
|
227
|
+
),
|
|
228
|
+
"OwnerID": add_comment(
|
|
229
|
+
getattr(item, "owner_id", ""), "OwnerID"
|
|
230
|
+
),
|
|
231
|
+
"BillAccountName": add_comment(
|
|
232
|
+
getattr(item, "bill_account_name", ""),
|
|
233
|
+
"BillAccountName",
|
|
234
|
+
),
|
|
235
|
+
"RecordID": add_comment(
|
|
236
|
+
getattr(item, "record_id", ""), "RecordID"
|
|
237
|
+
),
|
|
238
|
+
"CashAmount": add_comment(
|
|
239
|
+
getattr(item, "cash_amount", 0), "CashAmount"
|
|
240
|
+
),
|
|
241
|
+
}
|
|
242
|
+
formatted_response["Data"]["Items"]["Item"].append(item_data)
|
|
243
|
+
|
|
244
|
+
return formatted_response
|
|
245
|
+
|
|
246
|
+
except Exception as e:
|
|
247
|
+
feishu_alert.send(traceback.format_exc())
|
|
248
|
+
error_response = {
|
|
249
|
+
"Message": add_comment(f"Format error: {str(e)}", "Message"),
|
|
250
|
+
"RequestId": add_comment(
|
|
251
|
+
getattr(response.body, "request_id", ""), "RequestId"
|
|
252
|
+
),
|
|
253
|
+
"Data": {},
|
|
254
|
+
"Code": add_comment("Error", "Code"),
|
|
255
|
+
"Success": add_comment(False, "Success"),
|
|
256
|
+
}
|
|
257
|
+
return error_response
|
|
258
|
+
|
|
259
|
+
def _query_bill_once(
|
|
260
|
+
self,
|
|
261
|
+
billing_cycle: str,
|
|
262
|
+
max_results: int = 20,
|
|
263
|
+
next_token: str = None,
|
|
264
|
+
product_code: str = None,
|
|
265
|
+
subscription_type: str = None,
|
|
266
|
+
product_type: str = None,
|
|
267
|
+
is_hide_zero_charge: bool = False,
|
|
268
|
+
) -> dict:
|
|
269
|
+
"""
|
|
270
|
+
description:
|
|
271
|
+
查询阿里云账单信息
|
|
272
|
+
parameters:
|
|
273
|
+
billing_cycle: 账单周期,格式:YYYY-MM,默认:2025-08
|
|
274
|
+
max_results: 最大返回记录数,默认:20
|
|
275
|
+
next_token: 下一页的token,用于分页查询
|
|
276
|
+
product_code: 产品代码,可选
|
|
277
|
+
subscription_type: 订阅类型,可选值:Subscription(预付费)、PayAsYouGo(后付费)
|
|
278
|
+
product_type: 产品类型,可选
|
|
279
|
+
is_hide_zero_charge: 是否隐藏零费用记录,默认:False
|
|
280
|
+
return:
|
|
281
|
+
dict: 格式化后的响应数据
|
|
282
|
+
返回格式化的JSON响应,包含以下字段:
|
|
283
|
+
- Message: 错误信息
|
|
284
|
+
- RequestId: 请求ID
|
|
285
|
+
- Data: 返回数据
|
|
286
|
+
- BillingCycle: 账期
|
|
287
|
+
- TotalCount: 总记录数
|
|
288
|
+
- AccountID: 账号ID
|
|
289
|
+
- AccountName: 账号名称
|
|
290
|
+
- MaxResults: 本次请求所返回的最大记录数
|
|
291
|
+
- NextToken: 用来表示当前调用返回读取到的位置,空代表数据已经读取完毕
|
|
292
|
+
- Items: 账单详情列表
|
|
293
|
+
- Item: 账单详情数组,包含产品名称、费用信息、支付状态等详细字段
|
|
294
|
+
"""
|
|
295
|
+
# 构建请求参数
|
|
296
|
+
request_params = {
|
|
297
|
+
"billing_cycle": billing_cycle,
|
|
298
|
+
"max_results": max_results,
|
|
299
|
+
"is_hide_zero_charge": is_hide_zero_charge,
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
# 添加可选参数
|
|
303
|
+
if next_token:
|
|
304
|
+
request_params["next_token"] = next_token
|
|
305
|
+
if product_code:
|
|
306
|
+
request_params["product_code"] = product_code
|
|
307
|
+
if subscription_type:
|
|
308
|
+
request_params["subscription_type"] = subscription_type
|
|
309
|
+
if product_type:
|
|
310
|
+
request_params["product_type"] = product_type
|
|
311
|
+
|
|
312
|
+
query_settle_bill_request = bss_open_api_20171214_models.QuerySettleBillRequest(
|
|
313
|
+
**request_params
|
|
314
|
+
)
|
|
315
|
+
runtime = util_models.RuntimeOptions()
|
|
316
|
+
try:
|
|
317
|
+
# 调用 API 并获取返回结果
|
|
318
|
+
response = self.client.query_settle_bill_with_options(
|
|
319
|
+
query_settle_bill_request, runtime
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
# 格式化响应并返回带注释的JSON格式
|
|
323
|
+
formatted_response = self._format_bill_response(
|
|
324
|
+
response, with_comments=True
|
|
325
|
+
)
|
|
326
|
+
return formatted_response
|
|
327
|
+
|
|
328
|
+
except Exception as error:
|
|
329
|
+
feishu_alert.send(traceback.format_exc())
|
|
330
|
+
error_msg = getattr(error, "message", str(error))
|
|
331
|
+
# 诊断地址
|
|
332
|
+
diagnose_url = ""
|
|
333
|
+
if hasattr(error, "data") and error.data:
|
|
334
|
+
diagnose_url = error.data.get("Recommend", "")
|
|
335
|
+
|
|
336
|
+
# 返回错误响应
|
|
337
|
+
error_response = {
|
|
338
|
+
"Message": f"错误信息: {error_msg}",
|
|
339
|
+
"RequestId": "",
|
|
340
|
+
"Data": {},
|
|
341
|
+
"Code": "Error",
|
|
342
|
+
"Success": False,
|
|
343
|
+
"DiagnoseUrl": diagnose_url,
|
|
344
|
+
}
|
|
345
|
+
return error_response
|
|
346
|
+
|
|
347
|
+
def query_bill(self, billing_cycle: str, batch_size: int = 300):
|
|
348
|
+
"""
|
|
349
|
+
description:
|
|
350
|
+
按月份查询阿里云账单信息,会自动分页查询
|
|
351
|
+
parameters:
|
|
352
|
+
billing_cycle: 账单周期,格式:YYYY-MM,默认:2025-08
|
|
353
|
+
batch_size: 每页返回的记录数,默认:300
|
|
354
|
+
return:
|
|
355
|
+
dict: 格式化后的响应数据
|
|
356
|
+
"""
|
|
357
|
+
response = self._query_bill_once(billing_cycle=billing_cycle)
|
|
358
|
+
bill_data = response.get("Data", {}).get("Items", {}).get("Item", [])
|
|
359
|
+
next_token = response.get("Data", {}).get("NextToken")
|
|
360
|
+
while next_token:
|
|
361
|
+
response = self._query_bill_once(
|
|
362
|
+
billing_cycle=billing_cycle,
|
|
363
|
+
next_token=next_token,
|
|
364
|
+
max_results=batch_size,
|
|
365
|
+
)
|
|
366
|
+
bill_data.extend(response.get("Data", {}).get("Items", {}).get("Item", []))
|
|
367
|
+
next_token = response.get("Data", {}).get("NextToken")
|
|
368
|
+
return bill_data
|
|
369
|
+
|
|
370
|
+
def save_bill(
|
|
371
|
+
self,
|
|
372
|
+
bill_data: dict,
|
|
373
|
+
output_path: str,
|
|
374
|
+
file_format: str = "csv",
|
|
375
|
+
translate_headers: bool = False,
|
|
376
|
+
) -> str:
|
|
377
|
+
"""
|
|
378
|
+
description:
|
|
379
|
+
将query_bill的输出结果保存为表格文件
|
|
380
|
+
parameters:
|
|
381
|
+
bill_data: query_bill方法返回的账单数据
|
|
382
|
+
output_path: 输出文件路径
|
|
383
|
+
file_format: 文件格式,支持 'excel', 'csv', 'json',默认 'excel'
|
|
384
|
+
translate_headers: 是否将表头翻译为中文,默认 False
|
|
385
|
+
return:
|
|
386
|
+
str: 保存的文件路径
|
|
387
|
+
"""
|
|
388
|
+
try:
|
|
389
|
+
# 内部工具:清洗字符串
|
|
390
|
+
# 1) 去除注释(例如 "value # 注释" -> "value")
|
|
391
|
+
# 2) 去除首尾空白,并将内部连续空白压缩为一个空格
|
|
392
|
+
# 3) 去除常见的不可见空白字符(如不间断空格、零宽空格)
|
|
393
|
+
def _clean_string(value):
|
|
394
|
+
if not isinstance(value, str):
|
|
395
|
+
return value
|
|
396
|
+
# 去注释
|
|
397
|
+
if " # " in value:
|
|
398
|
+
value = value.split(" # ", 1)[0]
|
|
399
|
+
# 替换不可见空白
|
|
400
|
+
value = (
|
|
401
|
+
value.replace("\u00a0", " ")
|
|
402
|
+
.replace("\u200b", "")
|
|
403
|
+
.replace("\u200c", "")
|
|
404
|
+
.replace("\u200d", "")
|
|
405
|
+
)
|
|
406
|
+
# 规范化空白:先strip,再将内部所有空白替换为下划线
|
|
407
|
+
value = value.strip()
|
|
408
|
+
if value:
|
|
409
|
+
# 将任意连续空白缩为一个空格
|
|
410
|
+
# 1) 将任意连续空白(空格、制表符等)替换为单个下划线
|
|
411
|
+
value = re.sub(r"\s+", "_", value)
|
|
412
|
+
return value
|
|
413
|
+
|
|
414
|
+
# 创建DataFrame
|
|
415
|
+
df = pd.DataFrame(bill_data)
|
|
416
|
+
# 清洗字符串
|
|
417
|
+
df = df.applymap(_clean_string)
|
|
418
|
+
# 将NaN替换为空字符串,确保表格为空显示空,不是NaN
|
|
419
|
+
df = df.fillna("")
|
|
420
|
+
|
|
421
|
+
# 可选:根据注释映射翻译表头为中文
|
|
422
|
+
if translate_headers:
|
|
423
|
+
header_map = {
|
|
424
|
+
# 公共字段
|
|
425
|
+
"BillingCycle": "账期",
|
|
426
|
+
"AccountID": "账号ID",
|
|
427
|
+
"AccountName": "账号名称",
|
|
428
|
+
# 账单项字段(与 _format_bill_response 中注释一致)
|
|
429
|
+
"ProductName": "产品名称",
|
|
430
|
+
"SubOrderId": "订单明细ID",
|
|
431
|
+
"BillAccountID": "账单所属账号ID",
|
|
432
|
+
"DeductedByCashCoupons": "代金券折扣",
|
|
433
|
+
"PaymentTime": "订单支付时间",
|
|
434
|
+
"PaymentAmount": "现金支付(含信用额度退款抵扣)",
|
|
435
|
+
"DeductedByPrepaidCard": "储值卡抵扣",
|
|
436
|
+
"InvoiceDiscount": "优惠金额",
|
|
437
|
+
"UsageEndTime": "账单结束时间",
|
|
438
|
+
"Item": "账单类型",
|
|
439
|
+
"SubscriptionType": "订阅类型",
|
|
440
|
+
"PretaxGrossAmount": "原始金额",
|
|
441
|
+
"Currency": "货币类型",
|
|
442
|
+
"CommodityCode": "商品Code",
|
|
443
|
+
"UsageStartTime": "账单开始时间",
|
|
444
|
+
"AdjustAmount": "信用额度退款抵扣",
|
|
445
|
+
"Status": "支付状态",
|
|
446
|
+
"DeductedByCoupons": "优惠券抵扣",
|
|
447
|
+
"RoundDownDiscount": "抹零优惠",
|
|
448
|
+
"ProductDetail": "产品明细",
|
|
449
|
+
"ProductCode": "产品代码",
|
|
450
|
+
"ProductType": "产品类型",
|
|
451
|
+
"OutstandingAmount": "未结清金额",
|
|
452
|
+
"BizType": "业务类型",
|
|
453
|
+
"PipCode": "产品Code",
|
|
454
|
+
"PretaxAmount": "应付金额",
|
|
455
|
+
"OwnerID": "自帐号AccountID",
|
|
456
|
+
"BillAccountName": "账单所属账号名称",
|
|
457
|
+
"RecordID": "订单号/账单号",
|
|
458
|
+
"CashAmount": "现金支付(不含信用额度退款抵扣)",
|
|
459
|
+
}
|
|
460
|
+
# 仅重命名存在的列
|
|
461
|
+
df = df.rename(
|
|
462
|
+
columns={k: v for k, v in header_map.items() if k in df.columns}
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
# 保存文件
|
|
466
|
+
if file_format == "excel":
|
|
467
|
+
df.to_excel(output_path, index=False, engine="openpyxl")
|
|
468
|
+
elif file_format == "csv":
|
|
469
|
+
df.to_csv(output_path, index=False, encoding="utf-8-sig")
|
|
470
|
+
elif file_format == "json":
|
|
471
|
+
df.to_json(output_path, orient="records", force_ascii=False, indent=2)
|
|
472
|
+
|
|
473
|
+
return output_path
|
|
474
|
+
|
|
475
|
+
except Exception as e:
|
|
476
|
+
feishu_alert.send(traceback.format_exc())
|
|
477
|
+
raise Exception(f"保存账单数据失败: {str(e)}")
|