Compare commits
2 Commits
7925cb97e5
...
61c80c80c0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
61c80c80c0 | ||
|
|
328f11bf13 |
3
.gitignore
vendored
3
.gitignore
vendored
@ -13,3 +13,6 @@ yarn-error.log*
|
|||||||
backend/__pycache__/*
|
backend/__pycache__/*
|
||||||
backend/venv/*
|
backend/venv/*
|
||||||
frontend/dist
|
frontend/dist
|
||||||
|
backend/*/__pycache__/*
|
||||||
|
backend/*/*/__pycache__/*
|
||||||
|
backend/*/*/*/__pycache__/*
|
||||||
|
|||||||
1
backend/app/__init__.py
Normal file
1
backend/app/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
# 应用包初始化文件
|
||||||
1
backend/app/api/__init__.py
Normal file
1
backend/app/api/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
# API包初始化文件
|
||||||
127
backend/app/api/routes.py
Normal file
127
backend/app/api/routes.py
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
from fastapi import APIRouter, HTTPException
|
||||||
|
from ..models.schemas import PriceRequest, PriceComparison, InstanceSearchRequest
|
||||||
|
from ..core.config import AWS_REGION_NAMES, AZURE_REGION_NAMES, ALIYUN_REGION_NAMES
|
||||||
|
from ..core.instance_data import get_instance_info
|
||||||
|
from ..services import calculate_price
|
||||||
|
from typing import Dict, List, Any
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/api")
|
||||||
|
|
||||||
|
@router.get("/regions")
|
||||||
|
async def get_regions(platform: str = "aws"):
|
||||||
|
"""获取指定平台的区域列表"""
|
||||||
|
result = []
|
||||||
|
|
||||||
|
if platform == "aws":
|
||||||
|
for key in AWS_REGION_NAMES.keys():
|
||||||
|
result.append({'code': key, 'name': AWS_REGION_NAMES[key]})
|
||||||
|
elif platform == "azure":
|
||||||
|
for key in AZURE_REGION_NAMES.keys():
|
||||||
|
result.append({'code': key, 'name': AZURE_REGION_NAMES[key]})
|
||||||
|
elif platform == "aliyun":
|
||||||
|
for key in ALIYUN_REGION_NAMES.keys():
|
||||||
|
result.append({'code': key, 'name': ALIYUN_REGION_NAMES[key]})
|
||||||
|
else:
|
||||||
|
raise HTTPException(status_code=400, detail=f"不支持的平台: {platform}")
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
@router.get("/instance-types")
|
||||||
|
async def get_instance_types(platform: str = "aws"):
|
||||||
|
"""获取指定平台的实例类型"""
|
||||||
|
try:
|
||||||
|
return get_instance_info(platform)
|
||||||
|
except ValueError as e:
|
||||||
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
|
|
||||||
|
@router.post("/search-instances")
|
||||||
|
async def search_instances(request: InstanceSearchRequest):
|
||||||
|
"""搜索符合条件的实例类型"""
|
||||||
|
try:
|
||||||
|
# 验证必填参数
|
||||||
|
if request.cpu_cores is None:
|
||||||
|
raise HTTPException(status_code=400, detail="cpu_cores是必填参数")
|
||||||
|
if request.memory_gb is None:
|
||||||
|
raise HTTPException(status_code=400, detail="memory_gb是必填参数")
|
||||||
|
if request.disk_gb is None:
|
||||||
|
raise HTTPException(status_code=400, detail="disk_gb是必填参数")
|
||||||
|
if request.region is None:
|
||||||
|
raise HTTPException(status_code=400, detail="region是必填参数")
|
||||||
|
|
||||||
|
# 获取实例信息
|
||||||
|
try:
|
||||||
|
instance_info = get_instance_info(request.platform)
|
||||||
|
except ValueError as e:
|
||||||
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
|
|
||||||
|
matching_instances = []
|
||||||
|
|
||||||
|
# 遍历所有实例类型
|
||||||
|
for instance_type, info in instance_info.items():
|
||||||
|
try:
|
||||||
|
# 检查是否满足CPU要求(严格匹配)
|
||||||
|
if request.cpu_cores and info['cpu'] != request.cpu_cores:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 检查是否满足内存要求(严格匹配)
|
||||||
|
if request.memory_gb and info['memory'] != request.memory_gb:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 计算价格
|
||||||
|
price_info = await calculate_price(
|
||||||
|
platform=request.platform,
|
||||||
|
instance_type=instance_type,
|
||||||
|
region=request.region,
|
||||||
|
disk_gb=request.disk_gb,
|
||||||
|
operating_system=request.operating_system
|
||||||
|
)
|
||||||
|
|
||||||
|
# 添加到匹配列表
|
||||||
|
matching_instances.append({
|
||||||
|
"instance_type": instance_type,
|
||||||
|
"description": info['description'],
|
||||||
|
"cpu": info['cpu'],
|
||||||
|
"memory": info['memory'],
|
||||||
|
"disk_gb": request.disk_gb,
|
||||||
|
"hourly_price": price_info['hourly_price'],
|
||||||
|
"monthly_price": price_info['monthly_price'],
|
||||||
|
"disk_monthly_price": price_info['disk_monthly_price'],
|
||||||
|
"total_monthly_price": price_info['total_monthly_price']
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
print(f"计算价格时出错: {str(e)}")
|
||||||
|
|
||||||
|
# 按总价格排序
|
||||||
|
matching_instances.sort(key=lambda x: x['total_monthly_price'])
|
||||||
|
|
||||||
|
return matching_instances
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"搜索实例时出错: {str(e)}")
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
@router.post("/compare-prices")
|
||||||
|
async def compare_prices(comparison: PriceComparison):
|
||||||
|
"""比较不同配置的价格"""
|
||||||
|
try:
|
||||||
|
results = []
|
||||||
|
for config in comparison.configurations:
|
||||||
|
# 根据请求中的平台参数调用对应的价格计算服务
|
||||||
|
# 这里假设需要扩展PriceRequest模型,添加platform字段
|
||||||
|
# 暂时默认为AWS
|
||||||
|
platform = getattr(config, 'platform', 'aws')
|
||||||
|
|
||||||
|
price = await calculate_price(
|
||||||
|
platform=platform,
|
||||||
|
instance_type=config.instance_type,
|
||||||
|
region=config.region,
|
||||||
|
operating_system=config.operating_system,
|
||||||
|
disk_gb=config.disk_gb if hasattr(config, 'disk_gb') else 0
|
||||||
|
)
|
||||||
|
results.append({
|
||||||
|
"configuration": config.dict(),
|
||||||
|
"price": price
|
||||||
|
})
|
||||||
|
return results
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
74
backend/app/core/config.py
Normal file
74
backend/app/core/config.py
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import os
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
# 加载环境变量
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
# AWS EBS定价(GP3)
|
||||||
|
AWS_PRICING_EBS = {
|
||||||
|
"us-east-1": 0.08,
|
||||||
|
"us-east-2": 0.08,
|
||||||
|
"us-west-1": 0.096,
|
||||||
|
"us-west-2": 0.08,
|
||||||
|
"af-south-1": 0.1047,
|
||||||
|
"ap-east-1": 0.1056,
|
||||||
|
"ap-south-1": 0.0912,
|
||||||
|
"ap-northeast-3": 0.096,
|
||||||
|
"ap-northeast-2": 0.0912,
|
||||||
|
"ap-southeast-1": 0.096,
|
||||||
|
"ap-southeast-2": 0.096,
|
||||||
|
"ap-northeast-1": 0.096,
|
||||||
|
"ca-central-1": 0.088,
|
||||||
|
"eu-central-1": 0.0952,
|
||||||
|
"eu-west-1": 0.088,
|
||||||
|
"eu-west-2": 0.0928,
|
||||||
|
"eu-west-3": 0.0928,
|
||||||
|
"eu-north-1": 0.0836,
|
||||||
|
"me-central-1": 0.0968,
|
||||||
|
"sa-east-1": 0.152,
|
||||||
|
}
|
||||||
|
|
||||||
|
# 区域中文名称映射
|
||||||
|
AWS_REGION_NAMES = {
|
||||||
|
"us-east-1": "美国东部 (弗吉尼亚北部)",
|
||||||
|
"us-east-2": "美国东部 (俄亥俄)",
|
||||||
|
"us-west-1": "美国西部 (加利福尼亚北部)",
|
||||||
|
"us-west-2": "美国西部 (俄勒冈)",
|
||||||
|
"ap-south-1": "亚太地区 (孟买)",
|
||||||
|
"ap-east-1": "亚太地区 (香港)",
|
||||||
|
"ap-northeast-1": "亚太地区 (东京)",
|
||||||
|
"ap-northeast-2": "亚太地区 (首尔)",
|
||||||
|
"ap-southeast-1": "亚太地区 (新加坡)",
|
||||||
|
"ap-southeast-2": "亚太地区 (悉尼)",
|
||||||
|
"ca-central-1": "加拿大 (中部)",
|
||||||
|
"eu-central-1": "欧洲 (法兰克福)",
|
||||||
|
"eu-west-1": "欧洲 (爱尔兰)",
|
||||||
|
"eu-west-2": "欧洲 (伦敦)",
|
||||||
|
"eu-west-3": "欧洲 (巴黎)",
|
||||||
|
"sa-east-1": "南美洲 (圣保罗)",
|
||||||
|
"me-central-1": "中东 (阿联酋)",
|
||||||
|
"eu-north-1": "欧洲 (斯德哥尔摩)",
|
||||||
|
"eu-west-4": "欧洲 (比利时)",
|
||||||
|
"eu-south-1": "欧洲 (米兰)",
|
||||||
|
"eu-west-5": "欧洲 (阿姆斯特丹)",
|
||||||
|
"eu-west-6": "欧洲 (华沙)",
|
||||||
|
"eu-west-7": "欧洲 (伦敦)",
|
||||||
|
"eu-west-8": "欧洲 (米兰)",
|
||||||
|
"eu-west-9": "欧洲 (马德里)",
|
||||||
|
"eu-west-10": "欧洲 (巴黎)",
|
||||||
|
"eu-west-11": "欧洲 (阿姆斯特丹)",
|
||||||
|
"eu-west-12": "欧洲 (米兰)",
|
||||||
|
"eu-west-13": "欧洲 (米兰)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 可以添加其他云平台的配置
|
||||||
|
AZURE_REGION_NAMES = {
|
||||||
|
# 待添加Azure区域
|
||||||
|
}
|
||||||
|
|
||||||
|
ALIYUN_REGION_NAMES = {
|
||||||
|
# 待添加阿里云区域
|
||||||
|
}
|
||||||
|
|
||||||
|
# 支持的平台列表
|
||||||
|
SUPPORTED_PLATFORMS = ["aws", "azure", "aliyun"]
|
||||||
113
backend/app/core/instance_data.py
Normal file
113
backend/app/core/instance_data.py
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
# EC2实例信息
|
||||||
|
AWS_INSTANCE_INFO = {
|
||||||
|
# T2 系列 - 入门级通用型
|
||||||
|
"t2.nano": {"cpu": 1, "memory": 0.5, "description": "入门级通用型实例,适合轻量级工作负载"},
|
||||||
|
"t2.micro": {"cpu": 1, "memory": 1, "description": "具成本效益的入门级实例"},
|
||||||
|
"t2.small": {"cpu": 1, "memory": 2, "description": "低成本通用实例"},
|
||||||
|
"t2.medium": {"cpu": 2, "memory": 4, "description": "中等负载通用实例"},
|
||||||
|
"t2.large": {"cpu": 2, "memory": 8, "description": "大型通用实例"},
|
||||||
|
"t2.xlarge": {"cpu": 4, "memory": 16, "description": "超大型通用实例"},
|
||||||
|
"t2.2xlarge": {"cpu": 8, "memory": 32, "description": "双倍超大型通用实例"},
|
||||||
|
|
||||||
|
# T3 系列 - 新一代通用型
|
||||||
|
"t3.nano": {"cpu": 1, "memory": 0.5, "description": "新一代入门级通用型实例"},
|
||||||
|
"t3.micro": {"cpu": 1, "memory": 1, "description": "新一代低成本通用实例"},
|
||||||
|
"t3.small": {"cpu": 1, "memory": 2, "description": "新一代小型通用实例"},
|
||||||
|
"t3.medium": {"cpu": 2, "memory": 4, "description": "新一代中等负载通用实例"},
|
||||||
|
"t3.large": {"cpu": 2, "memory": 8, "description": "新一代大型通用实例"},
|
||||||
|
"t3.xlarge": {"cpu": 4, "memory": 16, "description": "新一代超大型通用实例"},
|
||||||
|
"t3.2xlarge": {"cpu": 8, "memory": 32, "description": "新一代双倍超大型通用实例"},
|
||||||
|
|
||||||
|
# T3a 系列 - 新一代通用型
|
||||||
|
"t3a.nano": {"cpu": 1, "memory": 0.5, "description": "新一代入门级通用型实例"},
|
||||||
|
"t3a.micro": {"cpu": 1, "memory": 1, "description": "新一代低成本通用实例"},
|
||||||
|
"t3a.small": {"cpu": 1, "memory": 2, "description": "新一代小型通用实例"},
|
||||||
|
"t3a.medium": {"cpu": 2, "memory": 4, "description": "新一代中等负载通用实例"},
|
||||||
|
"t3a.large": {"cpu": 2, "memory": 8, "description": "新一代大型通用实例"},
|
||||||
|
"t3a.xlarge": {"cpu": 4, "memory": 16, "description": "新一代超大型通用实例"},
|
||||||
|
"t3a.2xlarge": {"cpu": 8, "memory": 32, "description": "新一代双倍超大型通用实例"},
|
||||||
|
|
||||||
|
# C5 系列 - 计算优化型
|
||||||
|
"c5.large": {"cpu": 2, "memory": 4, "description": "计算优化实例"},
|
||||||
|
"c5.xlarge": {"cpu": 4, "memory": 8, "description": "高性能计算优化实例"},
|
||||||
|
"c5.2xlarge": {"cpu": 8, "memory": 16, "description": "大规模计算优化实例"},
|
||||||
|
"c5.4xlarge": {"cpu": 16, "memory": 32, "description": "超大规模计算优化实例"},
|
||||||
|
"c5.9xlarge": {"cpu": 36, "memory": 72, "description": "高性能计算优化实例"},
|
||||||
|
"c5.12xlarge": {"cpu": 48, "memory": 96, "description": "大规模计算优化实例"},
|
||||||
|
"c5.18xlarge": {"cpu": 72, "memory": 144, "description": "超大规模计算优化实例"},
|
||||||
|
"c5.24xlarge": {"cpu": 96, "memory": 192, "description": "最大规模计算优化实例"},
|
||||||
|
|
||||||
|
|
||||||
|
# c6a 系列 - 新一代计算优化型
|
||||||
|
"c6a.large": {"cpu": 2, "memory": 4, "description": "新一代计算优化实例"},
|
||||||
|
"c6a.xlarge": {"cpu": 4, "memory": 8, "description": "新一代高性能计算优化实例"},
|
||||||
|
"c6a.2xlarge": {"cpu": 8, "memory": 16, "description": "新一代大规模计算优化实例"},
|
||||||
|
"c6a.4xlarge": {"cpu": 16, "memory": 32, "description": "新一代超大规模计算优化实例"},
|
||||||
|
"c6a.8xlarge": {"cpu": 32, "memory": 64, "description": "新一代高性能计算优化实例"},
|
||||||
|
"c6a.12xlarge": {"cpu": 48, "memory": 96, "description": "新一代大规模计算优化实例"},
|
||||||
|
"c6a.16xlarge": {"cpu": 64, "memory": 128, "description": "新一代超大规模计算优化实例"},
|
||||||
|
"c6a.24xlarge": {"cpu": 96, "memory": 192, "description": "新一代最大规模计算优化实例"},
|
||||||
|
"c6a.32xlarge": {"cpu": 128, "memory": 256, "description": "新一代最大规模计算优化实例"},
|
||||||
|
# R5 系列 - 内存优化型
|
||||||
|
"r5.large": {"cpu": 2, "memory": 16, "description": "内存优化实例"},
|
||||||
|
"r5.xlarge": {"cpu": 4, "memory": 32, "description": "高性能内存优化实例"},
|
||||||
|
"r5.2xlarge": {"cpu": 8, "memory": 64, "description": "大规模内存优化实例"},
|
||||||
|
"r5.4xlarge": {"cpu": 16, "memory": 128, "description": "超大规模内存优化实例"},
|
||||||
|
"r5.8xlarge": {"cpu": 32, "memory": 256, "description": "高性能内存优化实例"},
|
||||||
|
"r5.12xlarge": {"cpu": 48, "memory": 384, "description": "大规模内存优化实例"},
|
||||||
|
"r5.16xlarge": {"cpu": 64, "memory": 512, "description": "超大规模内存优化实例"},
|
||||||
|
"r5.24xlarge": {"cpu": 96, "memory": 768, "description": "最大规模内存优化实例"},
|
||||||
|
|
||||||
|
# R6a 系列 - 新一代内存优化型
|
||||||
|
"r6a.large": {"cpu": 2, "memory": 16, "description": "新一代内存优化实例"},
|
||||||
|
"r6a.xlarge": {"cpu": 4, "memory": 32, "description": "新一代高性能内存优化实例"},
|
||||||
|
"r6a.2xlarge": {"cpu": 8, "memory": 64, "description": "新一代大规模内存优化实例"},
|
||||||
|
"r6a.4xlarge": {"cpu": 16, "memory": 128, "description": "新一代超大规模内存优化实例"},
|
||||||
|
"r6a.8xlarge": {"cpu": 32, "memory": 256, "description": "新一代高性能内存优化实例"},
|
||||||
|
"r6a.12xlarge": {"cpu": 48, "memory": 384, "description": "新一代大规模内存优化实例"},
|
||||||
|
"r6a.16xlarge": {"cpu": 64, "memory": 512, "description": "新一代超大规模内存优化实例"},
|
||||||
|
"r6a.24xlarge": {"cpu": 96, "memory": 768, "description": "新一代最大规模内存优化实例"},
|
||||||
|
"r6a.32xlarge": {"cpu": 128, "memory": 1024, "description": "新一代最大规模内存优化实例"},
|
||||||
|
|
||||||
|
# M5 系列 - 通用型
|
||||||
|
"m5.large": {"cpu": 2, "memory": 8, "description": "平衡型计算和内存实例"},
|
||||||
|
"m5.xlarge": {"cpu": 4, "memory": 16, "description": "高性能平衡型实例"},
|
||||||
|
"m5.2xlarge": {"cpu": 8, "memory": 32, "description": "大规模工作负载平衡型实例"},
|
||||||
|
"m5.4xlarge": {"cpu": 16, "memory": 64, "description": "超大规模工作负载平衡型实例"},
|
||||||
|
"m5.8xlarge": {"cpu": 32, "memory": 128, "description": "高性能工作负载平衡型实例"},
|
||||||
|
"m5.12xlarge": {"cpu": 48, "memory": 192, "description": "大规模工作负载平衡型实例"},
|
||||||
|
"m5.16xlarge": {"cpu": 64, "memory": 256, "description": "超大规模工作负载平衡型实例"},
|
||||||
|
"m5.24xlarge": {"cpu": 96, "memory": 384, "description": "最大规模工作负载平衡型实例"},
|
||||||
|
|
||||||
|
# M6a 系列 - 新一代通用型
|
||||||
|
"m6a.large": {"cpu": 2, "memory": 8, "description": "新一代平衡型计算和内存实例"},
|
||||||
|
"m6a.xlarge": {"cpu": 4, "memory": 16, "description": "新一代高性能平衡型实例"},
|
||||||
|
"m6a.2xlarge": {"cpu": 8, "memory": 32, "description": "新一代大规模工作负载平衡型实例"},
|
||||||
|
"m6a.4xlarge": {"cpu": 16, "memory": 64, "description": "新一代超大规模工作负载平衡型实例"},
|
||||||
|
"m6a.8xlarge": {"cpu": 32, "memory": 128, "description": "新一代高性能工作负载平衡型实例"},
|
||||||
|
"m6a.12xlarge": {"cpu": 48, "memory": 192, "description": "新一代大规模工作负载平衡型实例"},
|
||||||
|
"m6a.16xlarge": {"cpu": 64, "memory": 256, "description": "新一代超大规模工作负载平衡型实例"},
|
||||||
|
"m6a.24xlarge": {"cpu": 96, "memory": 384, "description": "新一代最大规模工作负载平衡型实例"},
|
||||||
|
"m6a.32xlarge": {"cpu": 128, "memory": 512, "description": "新一代最大规模工作负载平衡型实例"}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Azure实例信息 - 待添加
|
||||||
|
AZURE_INSTANCE_INFO = {
|
||||||
|
# 占位符,后续可以添加Azure的实例类型
|
||||||
|
}
|
||||||
|
|
||||||
|
# 阿里云实例信息 - 待添加
|
||||||
|
ALIYUN_INSTANCE_INFO = {
|
||||||
|
# 占位符,后续可以添加阿里云的实例类型
|
||||||
|
}
|
||||||
|
|
||||||
|
# 获取指定平台的实例信息
|
||||||
|
def get_instance_info(platform: str = "aws"):
|
||||||
|
if platform == "aws":
|
||||||
|
return AWS_INSTANCE_INFO
|
||||||
|
elif platform == "azure":
|
||||||
|
return AZURE_INSTANCE_INFO
|
||||||
|
elif platform == "aliyun":
|
||||||
|
return ALIYUN_INSTANCE_INFO
|
||||||
|
else:
|
||||||
|
raise ValueError(f"不支持的平台: {platform}")
|
||||||
27
backend/app/main.py
Normal file
27
backend/app/main.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
from fastapi import FastAPI
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
from .api.routes import router
|
||||||
|
|
||||||
|
# 创建FastAPI应用
|
||||||
|
app = FastAPI(title="云计算价格计算器", description="支持多云平台的价格计算服务")
|
||||||
|
|
||||||
|
# 配置CORS
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=["*"],
|
||||||
|
allow_credentials=True,
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# 包含API路由
|
||||||
|
app.include_router(router)
|
||||||
|
|
||||||
|
# 主页路由
|
||||||
|
@app.get("/")
|
||||||
|
async def root():
|
||||||
|
return {"message": "欢迎使用云计算价格计算器API", "version": "1.0.0"}
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import uvicorn
|
||||||
|
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||||
22
backend/app/models/schemas.py
Normal file
22
backend/app/models/schemas.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
from pydantic import BaseModel
|
||||||
|
from typing import List, Optional, Dict
|
||||||
|
|
||||||
|
# 数据模型
|
||||||
|
class PriceRequest(BaseModel):
|
||||||
|
instance_type: str
|
||||||
|
region: str
|
||||||
|
operating_system: str
|
||||||
|
purchase_option: str
|
||||||
|
duration: Optional[int] = 1
|
||||||
|
disk_gb: Optional[int] = 0
|
||||||
|
|
||||||
|
class PriceComparison(BaseModel):
|
||||||
|
configurations: List[PriceRequest]
|
||||||
|
|
||||||
|
class InstanceSearchRequest(BaseModel):
|
||||||
|
cpu_cores: Optional[int] = None
|
||||||
|
memory_gb: Optional[float] = None
|
||||||
|
disk_gb: Optional[int] = None
|
||||||
|
region: Optional[str] = None
|
||||||
|
operating_system: Optional[str] = "Linux"
|
||||||
|
platform: Optional[str] = "aws" # 新增平台字段,默认为AWS
|
||||||
19
backend/app/services/__init__.py
Normal file
19
backend/app/services/__init__.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# 服务包初始化文件
|
||||||
|
from typing import Dict, Any
|
||||||
|
from .aws.pricing import calculate_ec2_price
|
||||||
|
from .azure.pricing import calculate_vm_price
|
||||||
|
from .aliyun.pricing import calculate_ecs_price
|
||||||
|
|
||||||
|
async def calculate_price(platform: str, instance_type: str, region: str,
|
||||||
|
operating_system: str = "Linux", disk_gb: int = 0) -> Dict[str, float]:
|
||||||
|
"""
|
||||||
|
统一的价格计算接口,根据不同平台调用不同的价格计算服务
|
||||||
|
"""
|
||||||
|
if platform == "aws":
|
||||||
|
return await calculate_ec2_price(instance_type, region, operating_system, disk_gb)
|
||||||
|
elif platform == "azure":
|
||||||
|
return await calculate_vm_price(instance_type, region, operating_system, disk_gb)
|
||||||
|
elif platform == "aliyun":
|
||||||
|
return await calculate_ecs_price(instance_type, region, operating_system, disk_gb)
|
||||||
|
else:
|
||||||
|
raise ValueError(f"不支持的平台: {platform}")
|
||||||
1
backend/app/services/aliyun/__init__.py
Normal file
1
backend/app/services/aliyun/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
# 阿里云服务包初始化文件
|
||||||
45
backend/app/services/aliyun/pricing.py
Normal file
45
backend/app/services/aliyun/pricing.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
from typing import Dict, Any, Optional
|
||||||
|
|
||||||
|
async def calculate_storage_price(region: str, disk_gb: int) -> float:
|
||||||
|
"""
|
||||||
|
计算阿里云存储价格
|
||||||
|
"""
|
||||||
|
# 这里需要实现阿里云云盘价格的计算逻辑
|
||||||
|
# 目前返回一个占位符价格
|
||||||
|
return 0.08 * disk_gb
|
||||||
|
|
||||||
|
async def calculate_ecs_price(instance_type: str, region: str, operating_system: str = "Linux",
|
||||||
|
disk_gb: int = 0) -> Dict[str, float]:
|
||||||
|
"""
|
||||||
|
计算阿里云ECS实例价格
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 这里需要实现具体的阿里云ECS价格计算逻辑
|
||||||
|
# 可以使用阿里云API或者自定义价格表
|
||||||
|
|
||||||
|
# 目前返回一个示例价格
|
||||||
|
price_per_hour = 0.05 # 示例价格
|
||||||
|
|
||||||
|
# 计算存储价格
|
||||||
|
disk_monthly_price = await calculate_storage_price(region, disk_gb) if disk_gb > 0 else 0
|
||||||
|
|
||||||
|
# 计算每月价格
|
||||||
|
monthly_price = price_per_hour * 730 # 730小时/月
|
||||||
|
total_monthly_price = monthly_price + disk_monthly_price
|
||||||
|
|
||||||
|
return {
|
||||||
|
"hourly_price": price_per_hour,
|
||||||
|
"monthly_price": monthly_price,
|
||||||
|
"disk_monthly_price": disk_monthly_price,
|
||||||
|
"total_monthly_price": total_monthly_price
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"计算阿里云价格时出错: {str(e)}")
|
||||||
|
# 返回默认值或者可以抛出异常
|
||||||
|
return {
|
||||||
|
"hourly_price": 0,
|
||||||
|
"monthly_price": 0,
|
||||||
|
"disk_monthly_price": 0,
|
||||||
|
"total_monthly_price": 0
|
||||||
|
}
|
||||||
1
backend/app/services/aws/__init__.py
Normal file
1
backend/app/services/aws/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
# AWS服务包初始化文件
|
||||||
90
backend/app/services/aws/pricing.py
Normal file
90
backend/app/services/aws/pricing.py
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import boto3
|
||||||
|
import json
|
||||||
|
from ...core.config import AWS_PRICING_EBS
|
||||||
|
from typing import Dict, Any, Optional
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
import os
|
||||||
|
|
||||||
|
# 加载环境变量
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
|
||||||
|
# 配置boto3使用环境变量
|
||||||
|
boto3.setup_default_session(
|
||||||
|
aws_access_key_id=os.getenv('AWS_ACCESS_KEY_ID'),
|
||||||
|
aws_secret_access_key=os.getenv('AWS_SECRET_ACCESS_KEY'),
|
||||||
|
region_name=os.getenv('AWS_DEFAULT_REGION')
|
||||||
|
)
|
||||||
|
|
||||||
|
async def calculate_ebs_price(region: str, disk_gb: int) -> float:
|
||||||
|
"""
|
||||||
|
计算EBS存储价格
|
||||||
|
"""
|
||||||
|
if region in AWS_PRICING_EBS:
|
||||||
|
price_dimensions = AWS_PRICING_EBS[region]
|
||||||
|
else:
|
||||||
|
price_dimensions = 0.1
|
||||||
|
|
||||||
|
return price_dimensions * disk_gb
|
||||||
|
|
||||||
|
async def calculate_ec2_price(instance_type: str, region: str, operating_system: str = "Linux",
|
||||||
|
disk_gb: int = 0) -> Dict[str, float]:
|
||||||
|
"""
|
||||||
|
计算EC2实例价格
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 创建Pricing API客户端
|
||||||
|
pricing_client = boto3.client('pricing', region_name='us-east-1')
|
||||||
|
|
||||||
|
# 构建基础过滤器
|
||||||
|
filters = [
|
||||||
|
{'Type': 'TERM_MATCH', 'Field': 'instanceType', 'Value': instance_type},
|
||||||
|
{'Type': 'TERM_MATCH', 'Field': 'regionCode', 'Value': region},
|
||||||
|
{'Type': 'TERM_MATCH', 'Field': 'productFamily', 'Value': 'Compute Instance'},
|
||||||
|
{'Type': 'TERM_MATCH', 'Field': 'serviceCode', 'Value': 'AmazonEC2'},
|
||||||
|
{'Type': 'TERM_MATCH', 'Field': 'tenancy', 'Value': 'Shared'},
|
||||||
|
{'Type': 'TERM_MATCH', 'Field': 'operatingSystem', 'Value': operating_system},
|
||||||
|
{'Type': 'TERM_MATCH', 'Field': 'preInstalledSw', 'Value': 'NA'},
|
||||||
|
{'Type': 'TERM_MATCH', 'Field': 'termType', 'Value': 'OnDemand'},
|
||||||
|
{'Type': 'TERM_MATCH', 'Field': 'capacitystatus', 'Value': 'Used'},
|
||||||
|
{'Type': 'TERM_MATCH', 'Field': 'currentGeneration', 'Value': 'Yes'}
|
||||||
|
]
|
||||||
|
|
||||||
|
# 获取实例价格
|
||||||
|
response = pricing_client.get_products(
|
||||||
|
ServiceCode='AmazonEC2',
|
||||||
|
Filters=filters,
|
||||||
|
MaxResults=1
|
||||||
|
)
|
||||||
|
|
||||||
|
if not response['PriceList']:
|
||||||
|
raise Exception(f"未找到实例 {instance_type} 的价格信息")
|
||||||
|
|
||||||
|
price_list = json.loads(response['PriceList'][0])
|
||||||
|
terms = price_list['terms']['OnDemand']
|
||||||
|
price_dimensions = list(terms.values())[0]['priceDimensions']
|
||||||
|
price_per_hour = float(list(price_dimensions.values())[0]['pricePerUnit']['USD'])
|
||||||
|
|
||||||
|
# 计算存储价格
|
||||||
|
disk_monthly_price = await calculate_ebs_price(region, disk_gb) if disk_gb > 0 else 0
|
||||||
|
|
||||||
|
# 计算每月价格
|
||||||
|
monthly_price = price_per_hour * 730 # 730小时/月
|
||||||
|
total_monthly_price = monthly_price + disk_monthly_price
|
||||||
|
|
||||||
|
return {
|
||||||
|
"hourly_price": price_per_hour,
|
||||||
|
"monthly_price": monthly_price,
|
||||||
|
"disk_monthly_price": disk_monthly_price,
|
||||||
|
"total_monthly_price": total_monthly_price
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"计算AWS价格时出错: {str(e)}")
|
||||||
|
# 返回默认值或者可以抛出异常
|
||||||
|
# return {
|
||||||
|
# "hourly_price": 0,
|
||||||
|
# "monthly_price": 0,
|
||||||
|
# "disk_monthly_price": 0,
|
||||||
|
# "total_monthly_price": 0
|
||||||
|
# }
|
||||||
1
backend/app/services/azure/__init__.py
Normal file
1
backend/app/services/azure/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
# Azure服务包初始化文件
|
||||||
45
backend/app/services/azure/pricing.py
Normal file
45
backend/app/services/azure/pricing.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
from typing import Dict, Any, Optional
|
||||||
|
|
||||||
|
async def calculate_storage_price(region: str, disk_gb: int) -> float:
|
||||||
|
"""
|
||||||
|
计算Azure存储价格
|
||||||
|
"""
|
||||||
|
# 这里需要实现Azure存储价格的计算逻辑
|
||||||
|
# 目前返回一个占位符价格
|
||||||
|
return 0.1 * disk_gb
|
||||||
|
|
||||||
|
async def calculate_vm_price(instance_type: str, region: str, operating_system: str = "Linux",
|
||||||
|
disk_gb: int = 0) -> Dict[str, float]:
|
||||||
|
"""
|
||||||
|
计算Azure虚拟机价格
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 这里需要实现具体的Azure VM价格计算逻辑
|
||||||
|
# 可以使用Azure Retail Prices API或者自定义价格表
|
||||||
|
|
||||||
|
# 目前返回一个示例价格
|
||||||
|
price_per_hour = 0.1 # 示例价格
|
||||||
|
|
||||||
|
# 计算存储价格
|
||||||
|
disk_monthly_price = await calculate_storage_price(region, disk_gb) if disk_gb > 0 else 0
|
||||||
|
|
||||||
|
# 计算每月价格
|
||||||
|
monthly_price = price_per_hour * 730 # 730小时/月
|
||||||
|
total_monthly_price = monthly_price + disk_monthly_price
|
||||||
|
|
||||||
|
return {
|
||||||
|
"hourly_price": price_per_hour,
|
||||||
|
"monthly_price": monthly_price,
|
||||||
|
"disk_monthly_price": disk_monthly_price,
|
||||||
|
"total_monthly_price": total_monthly_price
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"计算Azure价格时出错: {str(e)}")
|
||||||
|
# 返回默认值或者可以抛出异常
|
||||||
|
return {
|
||||||
|
"hourly_price": 0,
|
||||||
|
"monthly_price": 0,
|
||||||
|
"disk_monthly_price": 0,
|
||||||
|
"total_monthly_price": 0
|
||||||
|
}
|
||||||
391
backend/main.py
391
backend/main.py
@ -1,393 +1,6 @@
|
|||||||
from fastapi import FastAPI, HTTPException
|
from app.main import app
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
|
||||||
from pydantic import BaseModel
|
|
||||||
from typing import List, Optional, Dict
|
|
||||||
import boto3
|
|
||||||
import json
|
|
||||||
from datetime import datetime, timedelta
|
|
||||||
import os
|
|
||||||
from dotenv import load_dotenv
|
|
||||||
|
|
||||||
# 加载环境变量
|
# 这个文件只是一个入口点,实际的应用逻辑在app/main.py中
|
||||||
load_dotenv()
|
|
||||||
|
|
||||||
# 配置boto3使用环境变量
|
|
||||||
boto3.setup_default_session(
|
|
||||||
aws_access_key_id=os.getenv('AWS_ACCESS_KEY_ID'),
|
|
||||||
aws_secret_access_key=os.getenv('AWS_SECRET_ACCESS_KEY'),
|
|
||||||
region_name=os.getenv('AWS_DEFAULT_REGION')
|
|
||||||
)
|
|
||||||
|
|
||||||
app = FastAPI()
|
|
||||||
|
|
||||||
# 配置CORS
|
|
||||||
app.add_middleware(
|
|
||||||
CORSMiddleware,
|
|
||||||
allow_origins=["*"],
|
|
||||||
allow_credentials=True,
|
|
||||||
allow_methods=["*"],
|
|
||||||
allow_headers=["*"],
|
|
||||||
)
|
|
||||||
|
|
||||||
pricing_ebs = {
|
|
||||||
"us-east-1": 0.08,
|
|
||||||
"us-east-2": 0.08,
|
|
||||||
"us-west-1": 0.096,
|
|
||||||
"us-west-2": 0.08,
|
|
||||||
"af-south-1": 0.1047,
|
|
||||||
"ap-east-1": 0.1056,
|
|
||||||
"ap-south-1": 0.0912,
|
|
||||||
"ap-northeast-3": 0.096,
|
|
||||||
"ap-northeast-2": 0.0912,
|
|
||||||
"ap-southeast-1": 0.096,
|
|
||||||
"ap-southeast-2": 0.096,
|
|
||||||
"ap-northeast-1": 0.096,
|
|
||||||
"ca-central-1": 0.088,
|
|
||||||
"eu-central-1": 0.0952,
|
|
||||||
"eu-west-1": 0.088,
|
|
||||||
"eu-west-2": 0.0928,
|
|
||||||
"eu-west-3": 0.0928,
|
|
||||||
"eu-north-1": 0.0836,
|
|
||||||
"me-central-1": 0.0968,
|
|
||||||
"sa-east-1": 0.152,
|
|
||||||
}
|
|
||||||
|
|
||||||
# 数据模型
|
|
||||||
class PriceRequest(BaseModel):
|
|
||||||
instance_type: str
|
|
||||||
region: str
|
|
||||||
operating_system: str
|
|
||||||
purchase_option: str
|
|
||||||
duration: Optional[int] = 1
|
|
||||||
|
|
||||||
class PriceComparison(BaseModel):
|
|
||||||
configurations: List[PriceRequest]
|
|
||||||
|
|
||||||
class InstanceSearchRequest(BaseModel):
|
|
||||||
cpu_cores: Optional[int] = None
|
|
||||||
memory_gb: Optional[float] = None
|
|
||||||
disk_gb: Optional[int] = None
|
|
||||||
region: Optional[str] = None
|
|
||||||
operating_system: Optional[str] = "Linux"
|
|
||||||
|
|
||||||
# EC2实例信息
|
|
||||||
instance_info = {
|
|
||||||
# T2 系列 - 入门级通用型
|
|
||||||
"t2.nano": {"cpu": 1, "memory": 0.5, "description": "入门级通用型实例,适合轻量级工作负载"},
|
|
||||||
"t2.micro": {"cpu": 1, "memory": 1, "description": "具成本效益的入门级实例"},
|
|
||||||
"t2.small": {"cpu": 1, "memory": 2, "description": "低成本通用实例"},
|
|
||||||
"t2.medium": {"cpu": 2, "memory": 4, "description": "中等负载通用实例"},
|
|
||||||
"t2.large": {"cpu": 2, "memory": 8, "description": "大型通用实例"},
|
|
||||||
"t2.xlarge": {"cpu": 4, "memory": 16, "description": "超大型通用实例"},
|
|
||||||
"t2.2xlarge": {"cpu": 8, "memory": 32, "description": "双倍超大型通用实例"},
|
|
||||||
|
|
||||||
# T3 系列 - 新一代通用型
|
|
||||||
"t3.nano": {"cpu": 1, "memory": 0.5, "description": "新一代入门级通用型实例"},
|
|
||||||
"t3.micro": {"cpu": 1, "memory": 1, "description": "新一代低成本通用实例"},
|
|
||||||
"t3.small": {"cpu": 1, "memory": 2, "description": "新一代小型通用实例"},
|
|
||||||
"t3.medium": {"cpu": 2, "memory": 4, "description": "新一代中等负载通用实例"},
|
|
||||||
"t3.large": {"cpu": 2, "memory": 8, "description": "新一代大型通用实例"},
|
|
||||||
"t3.xlarge": {"cpu": 4, "memory": 16, "description": "新一代超大型通用实例"},
|
|
||||||
"t3.2xlarge": {"cpu": 8, "memory": 32, "description": "新一代双倍超大型通用实例"},
|
|
||||||
|
|
||||||
# T3a 系列 - 新一代通用型
|
|
||||||
"t3a.nano": {"cpu": 1, "memory": 0.5, "description": "新一代入门级通用型实例"},
|
|
||||||
"t3a.micro": {"cpu": 1, "memory": 1, "description": "新一代低成本通用实例"},
|
|
||||||
"t3a.small": {"cpu": 1, "memory": 2, "description": "新一代小型通用实例"},
|
|
||||||
"t3a.medium": {"cpu": 2, "memory": 4, "description": "新一代中等负载通用实例"},
|
|
||||||
"t3a.large": {"cpu": 2, "memory": 8, "description": "新一代大型通用实例"},
|
|
||||||
"t3a.xlarge": {"cpu": 4, "memory": 16, "description": "新一代超大型通用实例"},
|
|
||||||
"t3a.2xlarge": {"cpu": 8, "memory": 32, "description": "新一代双倍超大型通用实例"},
|
|
||||||
|
|
||||||
# C5 系列 - 计算优化型
|
|
||||||
"c5.large": {"cpu": 2, "memory": 4, "description": "计算优化实例"},
|
|
||||||
"c5.xlarge": {"cpu": 4, "memory": 8, "description": "高性能计算优化实例"},
|
|
||||||
"c5.2xlarge": {"cpu": 8, "memory": 16, "description": "大规模计算优化实例"},
|
|
||||||
"c5.4xlarge": {"cpu": 16, "memory": 32, "description": "超大规模计算优化实例"},
|
|
||||||
"c5.9xlarge": {"cpu": 36, "memory": 72, "description": "高性能计算优化实例"},
|
|
||||||
"c5.12xlarge": {"cpu": 48, "memory": 96, "description": "大规模计算优化实例"},
|
|
||||||
"c5.18xlarge": {"cpu": 72, "memory": 144, "description": "超大规模计算优化实例"},
|
|
||||||
"c5.24xlarge": {"cpu": 96, "memory": 192, "description": "最大规模计算优化实例"},
|
|
||||||
|
|
||||||
|
|
||||||
# c6a 系列 - 新一代计算优化型
|
|
||||||
"c6a.large": {"cpu": 2, "memory": 4, "description": "新一代计算优化实例"},
|
|
||||||
"c6a.xlarge": {"cpu": 4, "memory": 8, "description": "新一代高性能计算优化实例"},
|
|
||||||
"c6a.2xlarge": {"cpu": 8, "memory": 16, "description": "新一代大规模计算优化实例"},
|
|
||||||
"c6a.4xlarge": {"cpu": 16, "memory": 32, "description": "新一代超大规模计算优化实例"},
|
|
||||||
"c6a.8xlarge": {"cpu": 32, "memory": 64, "description": "新一代高性能计算优化实例"},
|
|
||||||
"c6a.12xlarge": {"cpu": 48, "memory": 96, "description": "新一代大规模计算优化实例"},
|
|
||||||
"c6a.16xlarge": {"cpu": 64, "memory": 128, "description": "新一代超大规模计算优化实例"},
|
|
||||||
"c6a.24xlarge": {"cpu": 96, "memory": 192, "description": "新一代最大规模计算优化实例"},
|
|
||||||
"c6a.32xlarge": {"cpu": 128, "memory": 256, "description": "新一代最大规模计算优化实例"},
|
|
||||||
# R5 系列 - 内存优化型
|
|
||||||
"r5.large": {"cpu": 2, "memory": 16, "description": "内存优化实例"},
|
|
||||||
"r5.xlarge": {"cpu": 4, "memory": 32, "description": "高性能内存优化实例"},
|
|
||||||
"r5.2xlarge": {"cpu": 8, "memory": 64, "description": "大规模内存优化实例"},
|
|
||||||
"r5.4xlarge": {"cpu": 16, "memory": 128, "description": "超大规模内存优化实例"},
|
|
||||||
"r5.8xlarge": {"cpu": 32, "memory": 256, "description": "高性能内存优化实例"},
|
|
||||||
"r5.12xlarge": {"cpu": 48, "memory": 384, "description": "大规模内存优化实例"},
|
|
||||||
"r5.16xlarge": {"cpu": 64, "memory": 512, "description": "超大规模内存优化实例"},
|
|
||||||
"r5.24xlarge": {"cpu": 96, "memory": 768, "description": "最大规模内存优化实例"},
|
|
||||||
|
|
||||||
# R6a 系列 - 新一代内存优化型
|
|
||||||
"r6a.large": {"cpu": 2, "memory": 16, "description": "新一代内存优化实例"},
|
|
||||||
"r6a.xlarge": {"cpu": 4, "memory": 32, "description": "新一代高性能内存优化实例"},
|
|
||||||
"r6a.2xlarge": {"cpu": 8, "memory": 64, "description": "新一代大规模内存优化实例"},
|
|
||||||
"r6a.4xlarge": {"cpu": 16, "memory": 128, "description": "新一代超大规模内存优化实例"},
|
|
||||||
"r6a.8xlarge": {"cpu": 32, "memory": 256, "description": "新一代高性能内存优化实例"},
|
|
||||||
"r6a.12xlarge": {"cpu": 48, "memory": 384, "description": "新一代大规模内存优化实例"},
|
|
||||||
"r6a.16xlarge": {"cpu": 64, "memory": 512, "description": "新一代超大规模内存优化实例"},
|
|
||||||
"r6a.24xlarge": {"cpu": 96, "memory": 768, "description": "新一代最大规模内存优化实例"},
|
|
||||||
"r6a.32xlarge": {"cpu": 128, "memory": 1024, "description": "新一代最大规模内存优化实例"},
|
|
||||||
|
|
||||||
# M5 系列 - 通用型
|
|
||||||
"m5.large": {"cpu": 2, "memory": 8, "description": "平衡型计算和内存实例"},
|
|
||||||
"m5.xlarge": {"cpu": 4, "memory": 16, "description": "高性能平衡型实例"},
|
|
||||||
"m5.2xlarge": {"cpu": 8, "memory": 32, "description": "大规模工作负载平衡型实例"},
|
|
||||||
"m5.4xlarge": {"cpu": 16, "memory": 64, "description": "超大规模工作负载平衡型实例"},
|
|
||||||
"m5.8xlarge": {"cpu": 32, "memory": 128, "description": "高性能工作负载平衡型实例"},
|
|
||||||
"m5.12xlarge": {"cpu": 48, "memory": 192, "description": "大规模工作负载平衡型实例"},
|
|
||||||
"m5.16xlarge": {"cpu": 64, "memory": 256, "description": "超大规模工作负载平衡型实例"},
|
|
||||||
"m5.24xlarge": {"cpu": 96, "memory": 384, "description": "最大规模工作负载平衡型实例"},
|
|
||||||
|
|
||||||
# M6a 系列 - 新一代通用型
|
|
||||||
"m6a.large": {"cpu": 2, "memory": 8, "description": "新一代平衡型计算和内存实例"},
|
|
||||||
"m6a.xlarge": {"cpu": 4, "memory": 16, "description": "新一代高性能平衡型实例"},
|
|
||||||
"m6a.2xlarge": {"cpu": 8, "memory": 32, "description": "新一代大规模工作负载平衡型实例"},
|
|
||||||
"m6a.4xlarge": {"cpu": 16, "memory": 64, "description": "新一代超大规模工作负载平衡型实例"},
|
|
||||||
"m6a.8xlarge": {"cpu": 32, "memory": 128, "description": "新一代高性能工作负载平衡型实例"},
|
|
||||||
"m6a.12xlarge": {"cpu": 48, "memory": 192, "description": "新一代大规模工作负载平衡型实例"},
|
|
||||||
"m6a.16xlarge": {"cpu": 64, "memory": 256, "description": "新一代超大规模工作负载平衡型实例"},
|
|
||||||
"m6a.24xlarge": {"cpu": 96, "memory": 384, "description": "新一代最大规模工作负载平衡型实例"},
|
|
||||||
"m6a.32xlarge": {"cpu": 128, "memory": 512, "description": "新一代最大规模工作负载平衡型实例"}
|
|
||||||
}
|
|
||||||
|
|
||||||
# 区域中文名称映射
|
|
||||||
region_names: Dict[str, str] = {
|
|
||||||
"us-east-1": "美国东部 (弗吉尼亚北部)",
|
|
||||||
"us-east-2": "美国东部 (俄亥俄)",
|
|
||||||
"us-west-1": "美国西部 (加利福尼亚北部)",
|
|
||||||
"us-west-2": "美国西部 (俄勒冈)",
|
|
||||||
"ap-south-1": "亚太地区 (孟买)",
|
|
||||||
"ap-east-1": "亚太地区 (香港)",
|
|
||||||
"ap-northeast-1": "亚太地区 (东京)",
|
|
||||||
"ap-northeast-2": "亚太地区 (首尔)",
|
|
||||||
"ap-southeast-1": "亚太地区 (新加坡)",
|
|
||||||
"ap-southeast-2": "亚太地区 (悉尼)",
|
|
||||||
"ca-central-1": "加拿大 (中部)",
|
|
||||||
"eu-central-1": "欧洲 (法兰克福)",
|
|
||||||
"eu-west-1": "欧洲 (爱尔兰)",
|
|
||||||
"eu-west-2": "欧洲 (伦敦)",
|
|
||||||
"eu-west-3": "欧洲 (巴黎)",
|
|
||||||
"sa-east-1": "南美洲 (圣保罗)",
|
|
||||||
"me-central-1": "中东 (阿联酋)",
|
|
||||||
"eu-north-1": "欧洲 (斯德哥尔摩)",
|
|
||||||
"eu-west-4": "欧洲 (比利时)",
|
|
||||||
"eu-south-1": "欧洲 (米兰)",
|
|
||||||
"eu-west-5": "欧洲 (阿姆斯特丹)",
|
|
||||||
"eu-west-6": "欧洲 (华沙)",
|
|
||||||
"eu-west-7": "欧洲 (伦敦)",
|
|
||||||
"eu-west-8": "欧洲 (米兰)",
|
|
||||||
"eu-west-9": "欧洲 (马德里)",
|
|
||||||
"eu-west-10": "欧洲 (巴黎)",
|
|
||||||
"eu-west-11": "欧洲 (阿姆斯特丹)",
|
|
||||||
"eu-west-12": "欧洲 (米兰)",
|
|
||||||
"eu-west-13": "欧洲 (米兰)",
|
|
||||||
}
|
|
||||||
|
|
||||||
# 获取EC2价格
|
|
||||||
@app.get("/api/regions")
|
|
||||||
async def get_regions():
|
|
||||||
# 从环境变量获取区域,如果未设置则使用默认区域
|
|
||||||
region = os.environ.get('AWS_DEFAULT_REGION', 'us-east-1')
|
|
||||||
ec2 = boto3.client('ec2', region_name=region)
|
|
||||||
|
|
||||||
try:
|
|
||||||
regions_response = ec2.describe_regions()
|
|
||||||
regions = [region['RegionName'] for region in regions_response['Regions']]
|
|
||||||
|
|
||||||
# 创建包含中文名称的结果
|
|
||||||
result = []
|
|
||||||
for region_code in regions:
|
|
||||||
region_info = {
|
|
||||||
"code": region_code,
|
|
||||||
"name": region_names.get(region_code, f"未知区域 ({region_code})")
|
|
||||||
}
|
|
||||||
result.append(region_info)
|
|
||||||
|
|
||||||
# 按照区域名称排序
|
|
||||||
result.sort(key=lambda x: x["name"])
|
|
||||||
return result
|
|
||||||
except Exception as e:
|
|
||||||
# 如果API调用失败,返回常用区域列表
|
|
||||||
print(f"Error fetching regions: {str(e)}")
|
|
||||||
result = []
|
|
||||||
for region_code in [
|
|
||||||
"us-east-1", "us-east-2", "us-west-1", "us-west-2",
|
|
||||||
"ap-south-1", "ap-northeast-1", "ap-northeast-2", "ap-southeast-1",
|
|
||||||
"ap-southeast-2", "ca-central-1", "eu-central-1", "eu-west-1",
|
|
||||||
"eu-west-2", "eu-west-3", "sa-east-1"
|
|
||||||
]:
|
|
||||||
region_info = {
|
|
||||||
"code": region_code,
|
|
||||||
"name": region_names.get(region_code, f"未知区域 ({region_code})")
|
|
||||||
}
|
|
||||||
result.append(region_info)
|
|
||||||
|
|
||||||
# 按照区域名称排序
|
|
||||||
result.sort(key=lambda x: x["name"])
|
|
||||||
return result
|
|
||||||
|
|
||||||
@app.get("/api/instance-types")
|
|
||||||
async def get_instance_types():
|
|
||||||
# 返回所有实例类型和它们的详细信息
|
|
||||||
return instance_info
|
|
||||||
|
|
||||||
@app.post("/api/search-instances")
|
|
||||||
async def search_instances(request: InstanceSearchRequest):
|
|
||||||
try:
|
|
||||||
matching_instances = []
|
|
||||||
print(f"request: {request}")
|
|
||||||
if request.cpu_cores is None:
|
|
||||||
raise HTTPException(status_code=500, detail=str("cpu_cores is required"))
|
|
||||||
if request.memory_gb is None:
|
|
||||||
raise HTTPException(status_code=500, detail=str("memory_gb is required"))
|
|
||||||
if request.disk_gb is None:
|
|
||||||
raise HTTPException(status_code=500, detail=str("disk_gb is required"))
|
|
||||||
if request.region is None:
|
|
||||||
raise HTTPException(status_code=500, detail=str("region is required"))
|
|
||||||
# 遍历所有实例类型
|
|
||||||
for instance_type, info in instance_info.items():
|
|
||||||
try:
|
|
||||||
# 检查是否满足CPU要求(严格匹配)
|
|
||||||
if request.cpu_cores and info['cpu'] != request.cpu_cores:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# 检查是否满足内存要求(严格匹配)
|
|
||||||
if request.memory_gb and info['memory'] != request.memory_gb:
|
|
||||||
continue
|
|
||||||
|
|
||||||
print(f"instance_type: {instance_type}")
|
|
||||||
# 计算价格
|
|
||||||
price_info = await calculate_price(
|
|
||||||
instance_type=instance_type,
|
|
||||||
region=request.region,
|
|
||||||
disk_gb=request.disk_gb,
|
|
||||||
operating_system=request.operating_system
|
|
||||||
)
|
|
||||||
|
|
||||||
# 添加到匹配列表
|
|
||||||
matching_instances.append({
|
|
||||||
"instance_type": instance_type,
|
|
||||||
"description": info['description'],
|
|
||||||
"cpu": info['cpu'],
|
|
||||||
"memory": info['memory'],
|
|
||||||
"disk_gb": request.disk_gb,
|
|
||||||
"hourly_price": price_info['hourly_price'],
|
|
||||||
"monthly_price": price_info['monthly_price'],
|
|
||||||
"disk_monthly_price": price_info['disk_monthly_price'],
|
|
||||||
"total_monthly_price": price_info['total_monthly_price']
|
|
||||||
})
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error calculating price: {str(e)}")
|
|
||||||
|
|
||||||
# 按总价格排序
|
|
||||||
matching_instances.sort(key=lambda x: x['total_monthly_price'])
|
|
||||||
|
|
||||||
return matching_instances
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error searching instances: {str(e)}")
|
|
||||||
# raise HTTPException(status_code=500, detail=str(e))
|
|
||||||
|
|
||||||
async def calculate_price(instance_type: str, region: str, disk_gb: int, operating_system: str = "Linux"):
|
|
||||||
print(f"operating_system: {operating_system}")
|
|
||||||
try:
|
|
||||||
# 创建Pricing API客户端
|
|
||||||
pricing_client = boto3.client('pricing', region_name='us-east-1')
|
|
||||||
|
|
||||||
# 构建基础过滤器
|
|
||||||
filters = [
|
|
||||||
{'Type': 'TERM_MATCH', 'Field': 'instanceType', 'Value': instance_type},
|
|
||||||
{'Type': 'TERM_MATCH', 'Field': 'regionCode', 'Value': region},
|
|
||||||
{'Type': 'TERM_MATCH', 'Field': 'productFamily', 'Value': 'Compute Instance'},
|
|
||||||
{'Type': 'TERM_MATCH', 'Field': 'serviceCode', 'Value': 'AmazonEC2'},
|
|
||||||
{'Type': 'TERM_MATCH', 'Field': 'tenancy', 'Value': 'Shared'},
|
|
||||||
{'Type': 'TERM_MATCH', 'Field': 'operatingSystem', 'Value': operating_system},
|
|
||||||
{'Type': 'TERM_MATCH', 'Field': 'preInstalledSw', 'Value': 'NA'},
|
|
||||||
{'Type': 'TERM_MATCH', 'Field': 'termType', 'Value': 'OnDemand'},
|
|
||||||
{'Type': 'TERM_MATCH', 'Field': 'capacitystatus', 'Value': 'Used'},
|
|
||||||
{'Type': 'TERM_MATCH', 'Field': 'currentGeneration', 'Value': 'Yes'}
|
|
||||||
]
|
|
||||||
|
|
||||||
# 根据操作系统设置不同的许可证模型
|
|
||||||
# if operating_system == "Windows":
|
|
||||||
# filters.append({'Type': 'TERM_MATCH', 'Field': 'licenseModel', 'Value': 'Windows'})
|
|
||||||
# else:
|
|
||||||
# filters.append({'Type': 'TERM_MATCH', 'Field': 'licenseModel', 'Value': 'No License required'})
|
|
||||||
|
|
||||||
# 获取实例价格
|
|
||||||
response = pricing_client.get_products(
|
|
||||||
ServiceCode='AmazonEC2',
|
|
||||||
Filters=filters,
|
|
||||||
MaxResults=1
|
|
||||||
)
|
|
||||||
|
|
||||||
if not response['PriceList']:
|
|
||||||
raise Exception(f"未找到实例 {instance_type} 的价格信息")
|
|
||||||
|
|
||||||
price_list = json.loads(response['PriceList'][0])
|
|
||||||
terms = price_list['terms']['OnDemand']
|
|
||||||
price_dimensions = list(terms.values())[0]['priceDimensions']
|
|
||||||
price_per_hour = float(list(price_dimensions.values())[0]['pricePerUnit']['USD'])
|
|
||||||
print(f"price_per_hour: {price_per_hour}")
|
|
||||||
# 计算GP3存储价格
|
|
||||||
storage_price_per_gb = calculate_gp3_price(region)
|
|
||||||
disk_monthly_price = storage_price_per_gb * disk_gb
|
|
||||||
|
|
||||||
# 计算每月价格
|
|
||||||
monthly_price = price_per_hour * 730
|
|
||||||
total_monthly_price = monthly_price + disk_monthly_price
|
|
||||||
|
|
||||||
return {
|
|
||||||
"hourly_price": price_per_hour,
|
|
||||||
"monthly_price": monthly_price,
|
|
||||||
"disk_monthly_price": disk_monthly_price,
|
|
||||||
"total_monthly_price": total_monthly_price
|
|
||||||
}
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error calculating price: {str(e)}")
|
|
||||||
# raise HTTPException(status_code=500, detail=str(e))
|
|
||||||
|
|
||||||
# 添加计算 GP3 存储价格的函数
|
|
||||||
def calculate_gp3_price(region: str) -> float:
|
|
||||||
if region in pricing_ebs.keys():
|
|
||||||
price_dimensions = pricing_ebs[region]
|
|
||||||
else:
|
|
||||||
price_dimensions = 0.1
|
|
||||||
|
|
||||||
return price_dimensions
|
|
||||||
|
|
||||||
@app.post("/api/compare-prices")
|
|
||||||
async def compare_prices(comparison: PriceComparison):
|
|
||||||
try:
|
|
||||||
results = []
|
|
||||||
for config in comparison.configurations:
|
|
||||||
price = await calculate_price(
|
|
||||||
config.instance_type,
|
|
||||||
config.region,
|
|
||||||
config.disk_gb,
|
|
||||||
config.operating_system
|
|
||||||
)
|
|
||||||
results.append({
|
|
||||||
"configuration": config.dict(),
|
|
||||||
"price": price
|
|
||||||
})
|
|
||||||
return results
|
|
||||||
except Exception as e:
|
|
||||||
raise HTTPException(status_code=500, detail=str(e))
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import uvicorn
|
import uvicorn
|
||||||
|
|||||||
@ -5,3 +5,5 @@ python-dotenv==1.0.0
|
|||||||
pydantic==2.4.2
|
pydantic==2.4.2
|
||||||
pandas==2.1.3
|
pandas==2.1.3
|
||||||
python-multipart==0.0.6
|
python-multipart==0.0.6
|
||||||
|
requests==2.31.0
|
||||||
|
httpx==0.25.2
|
||||||
Loading…
x
Reference in New Issue
Block a user