From d0b3ed2427dfe40d4ed8eaeb95ea16e8abcc564d Mon Sep 17 00:00:00 2001 From: wangqifan Date: Thu, 3 Apr 2025 12:52:37 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0MySQL=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=BA=93=E9=85=8D=E7=BD=AE=EF=BC=8C=E6=9B=B4=E6=96=B0=E5=90=8E?= =?UTF-8?q?=E7=AB=AFAPI=E4=BB=A5=E6=94=AF=E6=8C=81=E6=96=B0=E7=9A=84?= =?UTF-8?q?=E5=AE=9E=E4=BE=8B=E6=90=9C=E7=B4=A2=E5=8A=9F=E8=83=BD=EF=BC=8C?= =?UTF-8?q?=E5=B9=B6=E4=BF=AE=E6=94=B9=E5=89=8D=E7=AB=AF=E8=B0=83=E7=94=A8?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E4=BB=A5=E9=80=82=E5=BA=94=E6=96=B0API?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/.env | 8 +- backend/Dockerfile | 3 + backend/app/api/routes.py | 23 ++++- backend/app/models/schemas.py | 10 +- backend/app/services/aws/pricing_v2.py | 136 +++++++++++++++++++++++++ backend/requirements.txt | 3 +- frontend/src/api/index.js | 2 +- 7 files changed, 180 insertions(+), 5 deletions(-) create mode 100644 backend/app/services/aws/pricing_v2.py diff --git a/backend/.env b/backend/.env index c2c347a..f00336b 100644 --- a/backend/.env +++ b/backend/.env @@ -1,3 +1,9 @@ AWS_ACCESS_KEY_ID=AKIAVIOZF67K6HNCUJ5Y AWS_SECRET_ACCESS_KEY=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 -AWS_DEFAULT_REGION=us-east-1 \ No newline at end of file +AWS_DEFAULT_REGION=us-east-1 + +# MySQL数据库配置 +MYSQL_HOST=47.76.209.7 +MYSQL_USER=aws_price +MYSQL_PASSWORD=123456 +MYSQL_DATABASE=aws_price \ No newline at end of file diff --git a/backend/Dockerfile b/backend/Dockerfile index 268326a..b656dfc 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -5,6 +5,9 @@ WORKDIR /app # 安装系统依赖 RUN apt-get update && apt-get install -y \ curl \ + default-libmysqlclient-dev \ + gcc \ + pkg-config \ && rm -rf /var/lib/apt/lists/* # 复制依赖文件 diff --git a/backend/app/api/routes.py b/backend/app/api/routes.py index 7740a5e..3323d7e 100644 --- a/backend/app/api/routes.py +++ b/backend/app/api/routes.py @@ -1,8 +1,9 @@ from fastapi import APIRouter, HTTPException -from ..models.schemas import PriceRequest, PriceComparison, InstanceSearchRequest +from ..models.schemas import PriceRequest, PriceComparison, InstanceSearchRequest, InstanceSearchRequestV2 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 ..services.aws.pricing_v2 import search_instances_v2 from typing import Dict, List, Any router = APIRouter(prefix="/api") @@ -100,6 +101,26 @@ async def search_instances(request: InstanceSearchRequest): print(f"搜索实例时出错: {str(e)}") raise HTTPException(status_code=500, detail=str(e)) +@router.post("/search-instances-v2") +async def search_instances_v2_api(request: InstanceSearchRequestV2): + """ + 第二套搜索API - 通过MySQL数据库搜索符合条件的AWS实例 + """ + try: + # 调用服务层函数 + instances = await search_instances_v2( + region=request.region, + cpu_cores=request.cpu_cores, + memory_gb=request.memory_gb, + disk_gb=request.disk_gb, + operating_system=request.operating_system + ) + + return 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): """比较不同配置的价格""" diff --git a/backend/app/models/schemas.py b/backend/app/models/schemas.py index 0d00ed1..9b07adf 100644 --- a/backend/app/models/schemas.py +++ b/backend/app/models/schemas.py @@ -19,4 +19,12 @@ class InstanceSearchRequest(BaseModel): disk_gb: Optional[int] = None region: Optional[str] = None operating_system: Optional[str] = "Linux" - platform: Optional[str] = "aws" # 新增平台字段,默认为AWS \ No newline at end of file + platform: Optional[str] = "aws" # 新增平台字段,默认为AWS + +# 第二套价格计算API的数据模型 +class InstanceSearchRequestV2(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" \ No newline at end of file diff --git a/backend/app/services/aws/pricing_v2.py b/backend/app/services/aws/pricing_v2.py new file mode 100644 index 0000000..fbae1de --- /dev/null +++ b/backend/app/services/aws/pricing_v2.py @@ -0,0 +1,136 @@ +import mysql.connector +from mysql.connector import Error +import os +from dotenv import load_dotenv +from typing import List, Dict, Any, Optional +from ...core.config import AWS_REGION_NAMES_EN, AWS_PRICING_EBS + +# 加载环境变量 +load_dotenv() + +# 数据库连接配置 +DB_CONFIG = { + "host": os.getenv("MYSQL_HOST", "localhost"), + "user": os.getenv("MYSQL_USER", "root"), + "password": os.getenv("MYSQL_PASSWORD", ""), + "database": os.getenv("MYSQL_DATABASE", "aws_pricing") +} + +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 search_instances_v2( + region: Optional[str] = None, + cpu_cores: Optional[int] = None, + memory_gb: Optional[float] = None, + disk_gb: Optional[int] = None, + operating_system: str = "Linux" +) -> List[Dict[str, Any]]: + """ + 从MySQL数据库搜索符合条件的AWS实例 + """ + try: + # 获取区域的英文名称 + region_name = None + if region: + region_name = AWS_REGION_NAMES_EN.get(region) + + # 连接到MySQL数据库 + conn = mysql.connector.connect(**DB_CONFIG) + cursor = conn.cursor(dictionary=True) + + # 构建SQL查询 + query = """ + SELECT + id, + locations, + area_en, + area_cn, + instance_type, + price, + operating_system, + vcpu, + memory, + updatetime + FROM aws_price + WHERE 1=1 + """ + params = [] + + # 添加过滤条件 + if region_name: + query += " AND area_en = %s" + params.append(region_name) + + if cpu_cores: + query += " AND vcpu = %s" + params.append(cpu_cores) + + if memory_gb: + query += " AND memory = %s" + params.append(memory_gb) + + if operating_system: + query += " AND operating_system = %s" + params.append(operating_system) + + # 执行查询 + cursor.execute(query, params) + results = cursor.fetchall() + + # 处理结果 + instances = [] + for row in results: + hourly_price = float(row['price']) + monthly_price = hourly_price * 730 # 730小时/月 + + # 计算存储价格 + disk_monthly_price = 0 + if disk_gb and disk_gb > 0: + # 从区域代码获取区域英文名称,反向查找 + region_code = None + for code, name in AWS_REGION_NAMES_EN.items(): + if name == row['area_en']: + region_code = code + break + + disk_monthly_price = await calculate_ebs_price(region_code, disk_gb) if region_code else 0 + + # 计算总价格 + total_monthly_price = monthly_price + disk_monthly_price + + instances.append({ + "instance_type": row['instance_type'], + "description": f"{row['vcpu']}核 {row['memory']}GB {row['instance_type']}", + "cpu": row['vcpu'], + "memory": row['memory'], + "disk_gb": disk_gb if disk_gb else 0, + "hourly_price": hourly_price, + "monthly_price": monthly_price, + "disk_monthly_price": disk_monthly_price, + "total_monthly_price": total_monthly_price, + "region": row['area_en'], + "operating_system": row['operating_system'] + }) + + # 按总价格排序 + instances.sort(key=lambda x: x['total_monthly_price']) + + # 关闭连接 + cursor.close() + conn.close() + + return instances + except Error as e: + print(f"MySQL数据库错误: {e}") + raise Exception(f"数据库查询错误: {e}") + except Exception as e: + print(f"搜索实例时出错: {e}") + raise Exception(f"搜索实例时出错: {e}") \ No newline at end of file diff --git a/backend/requirements.txt b/backend/requirements.txt index 2bb6cd0..9dd9051 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -6,4 +6,5 @@ pydantic==2.4.2 pandas==2.1.3 python-multipart==0.0.6 requests==2.31.0 -httpx==0.25.2 \ No newline at end of file +httpx==0.25.2 +mysql-connector-python==8.2.0 \ No newline at end of file diff --git a/frontend/src/api/index.js b/frontend/src/api/index.js index feabdb8..4f46d01 100644 --- a/frontend/src/api/index.js +++ b/frontend/src/api/index.js @@ -63,7 +63,7 @@ const apiService = { // 搜索实例 searchInstances: async (params) => { try { - const response = await apiClient.post('/api/search-instances', params) + const response = await apiClient.post('/api/search-instances-v2', params) return response.data } catch (error) { console.error('搜索实例失败:', error)