Compare commits

...

2 Commits

Author SHA1 Message Date
frankkeres
61c80c80c0 Merge branch 'main' of http://47.99.201.80:3000/wangqifan/calc 2025-04-02 17:10:53 +08:00
frankkeres
328f11bf13 修改后端架构 2025-04-02 16:57:05 +08:00
17 changed files with 575 additions and 390 deletions

3
.gitignore vendored
View File

@ -13,3 +13,6 @@ yarn-error.log*
backend/__pycache__/*
backend/venv/*
frontend/dist
backend/*/__pycache__/*
backend/*/*/__pycache__/*
backend/*/*/*/__pycache__/*

1
backend/app/__init__.py Normal file
View File

@ -0,0 +1 @@
# 应用包初始化文件

View File

@ -0,0 +1 @@
# API包初始化文件

127
backend/app/api/routes.py Normal file
View 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))

View 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"]

View 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
View 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)

View 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

View 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}")

View File

@ -0,0 +1 @@
# 阿里云服务包初始化文件

View 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
}

View File

@ -0,0 +1 @@
# AWS服务包初始化文件

View 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
# }

View File

@ -0,0 +1 @@
# Azure服务包初始化文件

View 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
}

View File

@ -1,393 +1,6 @@
from fastapi import FastAPI, HTTPException
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
from app.main import app
# 加载环境变量
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))
# 这个文件只是一个入口点实际的应用逻辑在app/main.py中
if __name__ == "__main__":
import uvicorn

View File

@ -4,4 +4,6 @@ boto3==1.29.3
python-dotenv==1.0.0
pydantic==2.4.2
pandas==2.1.3
python-multipart==0.0.6
python-multipart==0.0.6
requests==2.31.0
httpx==0.25.2