aliyun-controller 0.1.0__tar.gz

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.
@@ -0,0 +1,12 @@
1
+ # venv
2
+ /venv/
3
+
4
+ # Environment variables
5
+ .env
6
+
7
+ # Python cache
8
+ __pycache__/
9
+ *.pyc
10
+
11
+ # RUnning Logfiles
12
+ *.log
@@ -0,0 +1,154 @@
1
+ Metadata-Version: 2.4
2
+ Name: aliyun-controller
3
+ Version: 0.1.0
4
+ Summary: A command-line tool for managing Alibaba Cloud services including billing queries and DNS management
5
+ Author-email: Moha-Master <hongkongreporter@outlook.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/Moha-Master/Aliyun-Controller
8
+ Project-URL: Repository, https://github.com/Moha-Master/Aliyun-Controller
9
+ Project-URL: Issues, https://github.com/Moha-Master/Aliyun-Controller/issues
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.8
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Requires-Python: >=3.8
20
+ Description-Content-Type: text/markdown
21
+ Requires-Dist: alibabacloud-ecs20140526
22
+ Requires-Dist: alibabacloud-bssopenapi20171214
23
+ Requires-Dist: alibabacloud-alidns20150109
24
+ Requires-Dist: InquirerPy
25
+ Requires-Dist: PyYAML
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=6.0; extra == "dev"
28
+ Requires-Dist: pytest-cov; extra == "dev"
29
+
30
+ # 阿里云控制台工具 (aliyun-controller)
31
+
32
+ 这是一个调用阿里云 Python SDK 在命令行中执行常用操作的简单工具,目前具有账单查询和 DNS 管理功能。
33
+
34
+ ## 功能特性
35
+
36
+ - **账单查询**:查询指定月份的阿里云账单总额和明细
37
+ - **流量统计**:查询指定月份的公网流出流量总量
38
+ - **DNS 管理**:管理域名解析记录,包括增删改查操作
39
+
40
+ ## 后续计划
41
+
42
+ - 加入**ECS控制**的相关内容
43
+ - 加入**OOS控制**的相关内容
44
+
45
+ ## 安装指南
46
+
47
+ ### 通过 pip 安装(推荐)
48
+
49
+ 可以通过 pip 直接安装:
50
+
51
+ ```bash
52
+ pip install aliyun-controller
53
+ ```
54
+
55
+ 安装后,可以直接使用 `aliyunctl` 命令运行程序:
56
+
57
+ ```bash
58
+ aliyunctl
59
+ ```
60
+
61
+ ### 从源码运行(开发模式)
62
+
63
+ 如果你希望进行二次开发或修改源码,可以从源码安装:
64
+
65
+ 1. 克隆此项目到本地:
66
+ ```bash
67
+ git clone <项目地址>
68
+ cd aliyun-controller
69
+ ```
70
+
71
+ 2. 创建虚拟环境(推荐):
72
+ ```bash
73
+ python -m venv venv
74
+ source venv/bin/activate # Linux/Mac
75
+ # 或在 Windows 上: venv\Scripts\activate
76
+ ```
77
+
78
+ 3. 安装依赖:
79
+ ```bash
80
+ pip install -e .
81
+ ```
82
+
83
+ 4. 创建阿里云 RAM 用户并授权:
84
+ - 登录阿里云控制台。
85
+ - 进入 RAM 访问控制。
86
+ - 在左侧导航栏选择 用户 > 创建用户。
87
+ - 设置登录名称和显示名称,勾选 为该用户自动生成AccessKey。
88
+ - 创建成功后,请务必保存好 AccessKey ID 和 AccessKey Secret,它们只显示一次。
89
+ - 为新创建的 RAM 用户授权:
90
+ - 在用户详情页,点击 添加权限。
91
+ - 选择 AliyunBSSReadOnlyAccess 和 AliyunDNSFullAccess 权限。
92
+ - 点击 确定 完成授权。
93
+
94
+ 5. 配置阿里云访问密钥:
95
+ 默认情况下,程序会在 `~/.configs/aliyun-controller` 目录下查找配置文件。
96
+ 你可以复制示例配置文件并修改:
97
+ ```bash
98
+ mkdir -p ~/.configs/aliyun-controller
99
+ cp config.yaml.example ~/.configs/aliyun-controller/config.yaml
100
+ ```
101
+ 然后编辑 `~/.configs/aliyun-controller/config.yaml` 文件,填入你创建的阿里云RAM用户的 AccessKey ID 和 AccessKey Secret:
102
+ ```yaml
103
+ access_key_id: your_access_key_id
104
+ access_key_secret: your_access_key_secret
105
+ ```
106
+
107
+ 你也可以使用 `--dir/-D` 参数指定配置文件所在的目录:
108
+ ```bash
109
+ aliyunctl -D /path/to/your/config/dir
110
+ ```
111
+
112
+ ## 使用方法
113
+
114
+ 安装后,可以直接使用 `aliyunctl` 命令运行程序:
115
+
116
+ ```bash
117
+ aliyunctl
118
+ ```
119
+
120
+ 程序将显示交互式菜单,你可以选择以下功能:
121
+
122
+ 1. **查询总流出流量**:查看指定月份的公网总流出流量
123
+ 2. **归纳账单**:查看指定月份的账单明细和总额
124
+ 3. **DNS解析管理**:管理域名解析记录
125
+
126
+ 你也可以使用 `--dir/-D` 参数指定配置文件所在的目录:
127
+
128
+ ```bash
129
+ aliyunctl -D /path/to/your/config/dir
130
+ ```
131
+
132
+ ### 账单查询
133
+
134
+ - 程序会默认查询当前月份的账单
135
+ - 你也可以输入其他月份(格式:YYYY-MM / YYYY-M)进行查询
136
+ - 支持分页查询和重新查询
137
+
138
+ ### DNS 管理
139
+
140
+ - 选择要管理的域名
141
+ - 查看所有解析记录
142
+ - 添加、编辑或删除解析记录
143
+ - 支持按不同方式排序记录(创建时间、二级域名、首字母)
144
+
145
+ ## 权限要求
146
+
147
+ 为了正常使用所有功能,你的阿里云 RAM 用户记得开放以下权限:
148
+
149
+ - `AliyunBSSReadOnlyAccess`:用于账单查询
150
+ - `AliyunDNSFullAccess`:用于 DNS 管理
151
+
152
+ ## 日志记录
153
+
154
+ 程序会在运行目录下生成 `app.log` 文件,记录操作日志和错误信息。
@@ -0,0 +1,125 @@
1
+ # 阿里云控制台工具 (aliyun-controller)
2
+
3
+ 这是一个调用阿里云 Python SDK 在命令行中执行常用操作的简单工具,目前具有账单查询和 DNS 管理功能。
4
+
5
+ ## 功能特性
6
+
7
+ - **账单查询**:查询指定月份的阿里云账单总额和明细
8
+ - **流量统计**:查询指定月份的公网流出流量总量
9
+ - **DNS 管理**:管理域名解析记录,包括增删改查操作
10
+
11
+ ## 后续计划
12
+
13
+ - 加入**ECS控制**的相关内容
14
+ - 加入**OOS控制**的相关内容
15
+
16
+ ## 安装指南
17
+
18
+ ### 通过 pip 安装(推荐)
19
+
20
+ 可以通过 pip 直接安装:
21
+
22
+ ```bash
23
+ pip install aliyun-controller
24
+ ```
25
+
26
+ 安装后,可以直接使用 `aliyunctl` 命令运行程序:
27
+
28
+ ```bash
29
+ aliyunctl
30
+ ```
31
+
32
+ ### 从源码运行(开发模式)
33
+
34
+ 如果你希望进行二次开发或修改源码,可以从源码安装:
35
+
36
+ 1. 克隆此项目到本地:
37
+ ```bash
38
+ git clone <项目地址>
39
+ cd aliyun-controller
40
+ ```
41
+
42
+ 2. 创建虚拟环境(推荐):
43
+ ```bash
44
+ python -m venv venv
45
+ source venv/bin/activate # Linux/Mac
46
+ # 或在 Windows 上: venv\Scripts\activate
47
+ ```
48
+
49
+ 3. 安装依赖:
50
+ ```bash
51
+ pip install -e .
52
+ ```
53
+
54
+ 4. 创建阿里云 RAM 用户并授权:
55
+ - 登录阿里云控制台。
56
+ - 进入 RAM 访问控制。
57
+ - 在左侧导航栏选择 用户 > 创建用户。
58
+ - 设置登录名称和显示名称,勾选 为该用户自动生成AccessKey。
59
+ - 创建成功后,请务必保存好 AccessKey ID 和 AccessKey Secret,它们只显示一次。
60
+ - 为新创建的 RAM 用户授权:
61
+ - 在用户详情页,点击 添加权限。
62
+ - 选择 AliyunBSSReadOnlyAccess 和 AliyunDNSFullAccess 权限。
63
+ - 点击 确定 完成授权。
64
+
65
+ 5. 配置阿里云访问密钥:
66
+ 默认情况下,程序会在 `~/.configs/aliyun-controller` 目录下查找配置文件。
67
+ 你可以复制示例配置文件并修改:
68
+ ```bash
69
+ mkdir -p ~/.configs/aliyun-controller
70
+ cp config.yaml.example ~/.configs/aliyun-controller/config.yaml
71
+ ```
72
+ 然后编辑 `~/.configs/aliyun-controller/config.yaml` 文件,填入你创建的阿里云RAM用户的 AccessKey ID 和 AccessKey Secret:
73
+ ```yaml
74
+ access_key_id: your_access_key_id
75
+ access_key_secret: your_access_key_secret
76
+ ```
77
+
78
+ 你也可以使用 `--dir/-D` 参数指定配置文件所在的目录:
79
+ ```bash
80
+ aliyunctl -D /path/to/your/config/dir
81
+ ```
82
+
83
+ ## 使用方法
84
+
85
+ 安装后,可以直接使用 `aliyunctl` 命令运行程序:
86
+
87
+ ```bash
88
+ aliyunctl
89
+ ```
90
+
91
+ 程序将显示交互式菜单,你可以选择以下功能:
92
+
93
+ 1. **查询总流出流量**:查看指定月份的公网总流出流量
94
+ 2. **归纳账单**:查看指定月份的账单明细和总额
95
+ 3. **DNS解析管理**:管理域名解析记录
96
+
97
+ 你也可以使用 `--dir/-D` 参数指定配置文件所在的目录:
98
+
99
+ ```bash
100
+ aliyunctl -D /path/to/your/config/dir
101
+ ```
102
+
103
+ ### 账单查询
104
+
105
+ - 程序会默认查询当前月份的账单
106
+ - 你也可以输入其他月份(格式:YYYY-MM / YYYY-M)进行查询
107
+ - 支持分页查询和重新查询
108
+
109
+ ### DNS 管理
110
+
111
+ - 选择要管理的域名
112
+ - 查看所有解析记录
113
+ - 添加、编辑或删除解析记录
114
+ - 支持按不同方式排序记录(创建时间、二级域名、首字母)
115
+
116
+ ## 权限要求
117
+
118
+ 为了正常使用所有功能,你的阿里云 RAM 用户记得开放以下权限:
119
+
120
+ - `AliyunBSSReadOnlyAccess`:用于账单查询
121
+ - `AliyunDNSFullAccess`:用于 DNS 管理
122
+
123
+ ## 日志记录
124
+
125
+ 程序会在运行目录下生成 `app.log` 文件,记录操作日志和错误信息。
@@ -0,0 +1,4 @@
1
+ """
2
+ Aliyun Controller
3
+ """
4
+ __version__ = "0.1.0"
@@ -0,0 +1,5 @@
1
+ # 阿里云访问密钥配置示例
2
+ # 复制此文件为 config.yaml 并填入你的实际密钥
3
+
4
+ access_key_id: your_access_key_id
5
+ access_key_secret: your_access_key_secret
@@ -0,0 +1,186 @@
1
+ import datetime
2
+ import re
3
+ import logging
4
+ import traceback
5
+ import argparse
6
+ import os
7
+ from pathlib import Path
8
+ from InquirerPy import prompt
9
+ from InquirerPy.base.control import Choice
10
+ from aliyun_controller.modules.billing import get_outbound_traffic_module, summarize_billing_module
11
+ from aliyun_controller.modules.dns import dns_management_module
12
+
13
+ # 配置日志
14
+ logging.basicConfig(
15
+ level=logging.INFO,
16
+ format='%(asctime)s - %(levelname)s - %(message)s',
17
+ handlers=[
18
+ logging.FileHandler("app.log", encoding='utf-8'),
19
+ logging.StreamHandler()
20
+ ]
21
+ )
22
+
23
+ def parse_args():
24
+ """解析命令行参数"""
25
+ parser = argparse.ArgumentParser(description="阿里云控制台工具")
26
+ parser.add_argument(
27
+ "-D", "--dir",
28
+ help="配置文件目录路径",
29
+ default=os.path.expanduser("~/.config/aliyun-controller")
30
+ )
31
+ return parser.parse_args()
32
+
33
+ def _prompt_for_billing_cycle() -> str | None:
34
+ """
35
+ 提示用户输入账单周期,并验证格式。
36
+ 支持 YYYY-MM 和 YYYY-M 格式。
37
+ 如果用户取消输入,则返回 None。
38
+ """
39
+ date_prompt = [
40
+ {
41
+ "type": "input",
42
+ "message": "请输入要查询的月份 (格式 YYYY-MM 或 YYYY-M):",
43
+ "name": "cycle",
44
+ "validate": lambda val: re.match(r"^\d{4}-(0?[1-9]|1[0-2])$", val) is not None,
45
+ "invalid_message": "格式错误,请输入 YYYY-MM 或 YYYY-M 格式的月份。"
46
+ }
47
+ ]
48
+ try:
49
+ answer = prompt(date_prompt)
50
+ if not answer: # 用户按 Ctrl+C
51
+ return None
52
+
53
+ cycle = answer.get("cycle")
54
+ year, month = cycle.split('-')
55
+ if len(month) == 1:
56
+ month = '0' + month
57
+ return f"{year}-{month}"
58
+ except KeyboardInterrupt:
59
+ return None
60
+ except Exception as e:
61
+ logging.error(f"输入月份时发生错误: {e}")
62
+ logging.debug(traceback.format_exc())
63
+ return None
64
+
65
+ def query_and_repeat(query_function):
66
+ """
67
+ 包装查询函数:先查当月,然后提供子菜单让用户选择继续查询或返回。
68
+ :param query_function: 接受 billing_cycle 参数的查询函数。
69
+ """
70
+ # 1. 默认查询当月
71
+ current_cycle = datetime.datetime.now().strftime("%Y-%m")
72
+ print(f"\n--- 正在查询默认月份 {current_cycle} 的账单 ---")
73
+ try:
74
+ query_function(current_cycle)
75
+ except Exception as e:
76
+ logging.error(f"查询账单时发生错误: {e}")
77
+ logging.debug(traceback.format_exc())
78
+ print(f"\n查询账单时发生错误,请查看日志了解详情。")
79
+
80
+ # 2. 进入子菜单循环
81
+ while True:
82
+ sub_menu_prompt = [
83
+ {
84
+ "type": "list",
85
+ "message": "请选择接下来的操作:",
86
+ "choices": [
87
+ Choice("set_date", name="查询其他月份"),
88
+ Choice("return", name="返回主菜单")
89
+ ],
90
+ "name": "sub_action"
91
+ }
92
+ ]
93
+
94
+ try:
95
+ result = prompt(sub_menu_prompt)
96
+ if not result: # 用户按 Ctrl+C
97
+ print("\n已返回主菜单。")
98
+ break
99
+
100
+ action = result.get("sub_action")
101
+ if action == "set_date":
102
+ new_cycle = _prompt_for_billing_cycle()
103
+ if new_cycle:
104
+ print(f"\n--- 正在查询 {new_cycle} 的账单 ---")
105
+ try:
106
+ query_function(new_cycle)
107
+ except Exception as e:
108
+ logging.error(f"查询账单时发生错误: {e}")
109
+ logging.debug(traceback.format_exc())
110
+ print(f"\n查询账单时发生错误,请查看日志了解详情。")
111
+ else:
112
+ print("\n输入已取消。")
113
+ continue # 重新显示子菜单
114
+ elif action == "return":
115
+ print("\n已返回主菜单。")
116
+ break
117
+ except Exception as e:
118
+ logging.error(f"执行操作时发生错误: {e}")
119
+ logging.debug(traceback.format_exc())
120
+ print(f"\n执行操作时发生错误,请查看日志了解详情。")
121
+ continue
122
+
123
+ def main():
124
+ """
125
+ 主函数,提供交互式菜单
126
+ """
127
+ args = parse_args()
128
+
129
+ # 设置配置目录环境变量,供模块使用
130
+ os.environ['ALIYUN_CONTROLLER_CONFIG_DIR'] = args.dir
131
+
132
+ print("阿里云控制台工具")
133
+ print("=" * 30)
134
+
135
+ while True:
136
+ questions = [
137
+ {
138
+ "type": "list",
139
+ "message": "请选择要执行的功能:",
140
+ "choices": [
141
+ Choice("get_traffic", name="1. 查询总流出流量"),
142
+ Choice("summarize_bill", name="2. 归纳账单"),
143
+ Choice("manage_dns", name="3. DNS解析管理"),
144
+ Choice(value=None, name="[退出]")
145
+ ],
146
+ "name": "action",
147
+ }
148
+ ]
149
+
150
+ try:
151
+ result = prompt(questions)
152
+ if not result:
153
+ print("\n已退出。")
154
+ break
155
+
156
+ action = result.get("action")
157
+
158
+ if action == "get_traffic":
159
+ query_and_repeat(get_outbound_traffic_module)
160
+ elif action == "summarize_bill":
161
+ query_and_repeat(summarize_billing_module)
162
+ elif action == "manage_dns":
163
+ try:
164
+ dns_management_module()
165
+ except Exception as e:
166
+ logging.error(f"DNS管理模块发生错误: {e}")
167
+ logging.debug(traceback.format_exc())
168
+ print(f"\nDNS管理模块发生错误,请查看日志了解详情。")
169
+ elif action is None:
170
+ print("已退出。")
171
+ break
172
+ except Exception as e:
173
+ logging.error(f"主菜单执行时发生错误: {e}")
174
+ logging.debug(traceback.format_exc())
175
+ print(f"\n执行操作时发生错误,请查看日志了解详情。")
176
+ continue
177
+
178
+ if __name__ == "__main__":
179
+ try:
180
+ main()
181
+ except KeyboardInterrupt:
182
+ print("\n\n检测到中断,程序已退出。")
183
+ except Exception as e:
184
+ logging.error(f"程序运行时发生未处理的错误: {e}")
185
+ logging.debug(traceback.format_exc())
186
+ print(f"\n程序运行时发生未处理的错误,请查看日志了解详情。")
@@ -0,0 +1,3 @@
1
+ """
2
+ Aliyun Controller Modules
3
+ """
@@ -0,0 +1,189 @@
1
+ import os
2
+ import yaml
3
+ from pathlib import Path
4
+ from alibabacloud_bssopenapi20171214.client import Client as BssOpenApi20171214Client
5
+ from alibabacloud_bssopenapi20171214.models import DescribeInstanceBillRequest
6
+ from alibabacloud_tea_openapi import models as open_api_models
7
+
8
+ def load_config():
9
+ """加载配置文件"""
10
+ config_dir = os.environ.get('ALIYUN_CONTROLLER_CONFIG_DIR',
11
+ os.path.expanduser('~/.config/aliyun-controller'))
12
+ config_path = Path(config_dir) / 'config.yaml'
13
+ example_path = Path(config_dir) / 'config.yaml.example'
14
+ package_example_path = Path(__file__).parent.parent / 'config.yaml.example'
15
+
16
+ # 检查配置目录是否存在,如果不存在则创建
17
+ config_path.parent.mkdir(parents=True, exist_ok=True)
18
+
19
+ # 如果示例文件不存在,则从包中复制
20
+ if not example_path.exists() and package_example_path.exists():
21
+ import shutil
22
+ shutil.copy(package_example_path, example_path)
23
+
24
+ # 如果配置文件不存在,则报错退出
25
+ if not config_path.exists():
26
+ print("配置文件不存在,请参考 config.yaml.example 创建 config.yaml 文件")
27
+ raise FileNotFoundError(f"配置文件不存在: {config_path}")
28
+
29
+ with open(config_path, 'r', encoding='utf-8') as f:
30
+ return yaml.safe_load(f)
31
+
32
+ class AliCloudBssQuerier:
33
+ def __init__(self):
34
+ """
35
+ 初始化客户端
36
+ """
37
+ config = load_config()
38
+ self.client = BssOpenApi20171214Client(
39
+ open_api_models.Config(
40
+ access_key_id=config['access_key_id'],
41
+ access_key_secret=config['access_key_secret'],
42
+ region_id="cn-hangzhou",
43
+ )
44
+ )
45
+
46
+ def fetch_bill_details(self, billing_cycle: str, subscription_type: str) -> list:
47
+ """
48
+ 根据指定的账单类型,分页获取所有账单明细。
49
+ """
50
+ all_items = []
51
+ next_token = None
52
+ try:
53
+ while True:
54
+ request = DescribeInstanceBillRequest(
55
+ billing_cycle=billing_cycle,
56
+ subscription_type=subscription_type,
57
+ is_billing_item=True,
58
+ max_results=300
59
+ )
60
+ if next_token:
61
+ request.next_token = next_token
62
+
63
+ response = self.client.describe_instance_bill(request)
64
+
65
+ response_dict = response.body.to_map()
66
+ data = response_dict.get('Data', {})
67
+ if not data:
68
+ break
69
+
70
+ items_list = data.get('Items', [])
71
+ all_items.extend(items_list)
72
+
73
+ next_token = data.get('NextToken')
74
+ if not next_token:
75
+ break
76
+
77
+ return all_items
78
+
79
+ except Exception as e:
80
+ print(f"\n查询 [{subscription_type}] 类型账单时出错: {e}")
81
+ return []
82
+
83
+ def fetch_all_bill_details(self, billing_cycle: str) -> list:
84
+ """
85
+ 获取所有类型的账单明细(PayAsYouGo + Subscription)
86
+ """
87
+ all_items = []
88
+ all_items.extend(self.fetch_bill_details(billing_cycle, 'PayAsYouGo'))
89
+ all_items.extend(self.fetch_bill_details(billing_cycle, 'Subscription'))
90
+ return all_items
91
+
92
+ def convert_usage_to_bytes(self, usage: float, unit: str) -> float:
93
+ """
94
+ 将用量转换为字节
95
+ """
96
+ unit = unit.upper()
97
+ if unit == 'GB':
98
+ return usage * 1024 * 1024 * 1024
99
+ elif unit == 'MB':
100
+ return usage * 1024 * 1024
101
+ elif unit == 'KB':
102
+ return usage * 1024
103
+ else:
104
+ return usage
105
+
106
+ def get_outbound_traffic_module(billing_cycle: str):
107
+ """
108
+ 流量查询模块
109
+ """
110
+ querier = AliCloudBssQuerier()
111
+ total_usage_bytes = 0.0
112
+
113
+ TRAFFIC_ITEMS_CODES = [
114
+ "ECS_Out_Bytes",
115
+ "Eip_Out_Bytes",
116
+ "Cdn_domestic_flow",
117
+ "Cdn_overseas_flow",
118
+ "OSS_Out_Traffic",
119
+ ]
120
+
121
+ print(f"\n正在查询账单周期 {billing_cycle} 的账单明细...")
122
+
123
+ all_items = querier.fetch_all_bill_details(billing_cycle)
124
+
125
+ if not all_items:
126
+ print("未发现任何账单明细。")
127
+ return
128
+
129
+ print("账单明细获取成功,开始计算总流量...")
130
+ for item in all_items:
131
+ if item.get('BillingItemCode') in TRAFFIC_ITEMS_CODES:
132
+ usage_str = item.get('Usage')
133
+ unit = (item.get('UsageUnit') or '').upper()
134
+ if usage_str:
135
+ try:
136
+ usage = float(usage_str)
137
+ if usage > 0:
138
+ usage_bytes = querier.convert_usage_to_bytes(usage, unit)
139
+ total_usage_bytes += usage_bytes
140
+ except ValueError:
141
+ continue
142
+
143
+ total_traffic_gb = total_usage_bytes / (1024 * 1024 * 1024)
144
+ print("\n" + "="*45)
145
+ print(f"账单周期 {billing_cycle} 的总公网流出流量: {total_traffic_gb:.4f} GB")
146
+ print("="*45)
147
+
148
+ def summarize_billing_module(billing_cycle: str):
149
+ """
150
+ 当月完整账单归纳模块
151
+ """
152
+ querier = AliCloudBssQuerier()
153
+ summary = {}
154
+
155
+ print(f"\n正在获取账单周期 {billing_cycle} 的所有账单明细...")
156
+ all_items = querier.fetch_all_bill_details(billing_cycle)
157
+
158
+ if not all_items:
159
+ print("未发现任何账单明细。")
160
+ return
161
+
162
+ for item in all_items:
163
+ product_code = item.get('ProductCode', 'Unknown')
164
+ product_name = item.get('ProductName', 'Unknown')
165
+ amount = float(item.get('PretaxAmount', 0.0))
166
+ if product_code not in summary:
167
+ summary[product_code] = {'product_name': product_name, 'total_amount': 0.0, 'count': 0}
168
+ summary[product_code]['product_name'] = product_name # 更新产品名称(同一产品代码可能有多个名称,取最后一个)
169
+ summary[product_code]['total_amount'] += amount
170
+ summary[product_code]['count'] += 1
171
+
172
+ # 按金额从大到小排序
173
+ sorted_summary = sorted(summary.items(), key=lambda x: x[1]['total_amount'], reverse=True)
174
+
175
+ print("\n" + "="*70)
176
+ print(f"账单周期 {billing_cycle} 消费归纳".center(70))
177
+ print("="*70)
178
+ print(f"{'产品名称':<25} {'产品代码':<15} {'账单条数':<10} {'总金额 (元)':<15}")
179
+ print("-"*70)
180
+
181
+ total_amount = 0.0
182
+ for product_code, data in sorted_summary:
183
+ total_amount += data['total_amount']
184
+ product_name = data['product_name'][:24] # 截断过长的产品名称
185
+ print(f"{product_name:<25} {product_code:<15} {data['count']:<10} {data['total_amount']:<15.2f}")
186
+
187
+ print("-"*70)
188
+ print(f"总计: {total_amount:.2f} 元".rjust(70))
189
+ print("="*70)