From efb053b27055be11a3ee3ab78de9ea4574b69b4d Mon Sep 17 00:00:00 2001 From: wangqifan Date: Thu, 3 Apr 2025 12:52:37 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=A0=E9=99=A4docker-compose.yml=E3=80=81?= =?UTF-8?q?=E5=90=8E=E7=AB=AF=E5=92=8C=E5=89=8D=E7=AB=AF=E7=9A=84Dockerfil?= =?UTF-8?q?e=EF=BC=8C=E7=AE=80=E5=8C=96=E9=A1=B9=E7=9B=AE=E7=BB=93?= =?UTF-8?q?=E6=9E=84=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 372 +++++++++---------------- backend/.env | 8 +- backend/Dockerfile | 23 -- backend/app/api/routes.py | 23 +- backend/app/models/schemas.py | 10 +- backend/app/services/aws/pricing_v2.py | 136 +++++++++ backend/requirements.txt | 3 +- docker-compose.yml | 69 ----- frontend/Dockerfile | 29 -- frontend/src/api/index.js | 2 +- 10 files changed, 307 insertions(+), 368 deletions(-) delete mode 100644 backend/Dockerfile create mode 100644 backend/app/services/aws/pricing_v2.py delete mode 100644 docker-compose.yml delete mode 100644 frontend/Dockerfile diff --git a/README.md b/README.md index 0e55017..02a8e43 100644 --- a/README.md +++ b/README.md @@ -4,325 +4,213 @@ ## 功能特点 -1. 价格计算器 +1. **价格计算器** - 选择实例类型、区域、操作系统和购买选项 - 实时计算价格 - 显示详细的价格信息 -2. 价格对比 - - 支持多个配置的价格对比 - - 表格和图表展示 - - 导出对比结果 +2. **实例搜索** + - 根据CPU、内存和其他规格查找最合适的实例 + - 支持从AWS官方API和数据库两种数据源查询 + - 显示完整规格和价格信息 -3. 预算估算 - - 支持自定义和预设使用时长 - - 显示月度成本趋势 - - 详细的成本明细 +3. **价格比较** + - 支持多个实例配置的并排比较 + - 生成标准化的报价单 + - 支持导出Excel格式报价单 ## 技术栈 -- 前端:Vue.js 3 + Element Plus + ECharts -- 后端:Python FastAPI + Boto3 -- 数据源:AWS API +- **前端**:Vue.js 3 + Element Plus +- **后端**:Python FastAPI +- **数据源**:AWS Pricing API + MySQL数据库 ## 系统要求 -- Docker 20.10+ -- Docker Compose 2.0+ +- Python 3.9+ - Node.js 14+ - npm 6+ +- MySQL 5.7+ -## 快速开始 - -1. 克隆项目并进入项目目录: -```bash -git clone -cd calc -``` - -2. 创建环境变量文件: -```bash -cp .env.example .env -``` -然后编辑 `.env` 文件,填入必要的环境变量。 - -3. 运行安装脚本: -```bash -chmod +x install.sh -./install.sh -``` - -安装脚本会自动: -- 检查必要的命令是否已安装 -- 创建必要的目录结构 -- 构建前端项目 -- 检查环境变量配置 -- 检查 SSL 证书(如果有) -- 启动所有服务 -- 检查服务状态 - -## 访问应用 - -安装完成后,可以通过以下地址访问应用: -- 前端界面:http://localhost -- 后端 API:http://localhost:8000 - -## 目录结构 +## 项目结构 ``` . -├── backend/ # 后端服务 -├── frontend/ # 前端项目 -├── nginx/ # Nginx 配置 -│ ├── conf.d/ # Nginx 配置文件 -│ └── ssl/ # SSL 证书目录 -├── docker-compose.yml -├── install.sh -└── README.md +├── backend/ # 后端项目 +│ ├── app/ # 应用代码 +│ │ ├── api/ # API路由 +│ │ ├── core/ # 核心配置 +│ │ ├── models/ # 数据模型 +│ │ └── services/ # 服务层 +│ ├── main.py # 入口文件 +│ └── requirements.txt # 依赖包 +└── frontend/ # 前端项目 + ├── public/ # 静态资源 + ├── src/ # 源代码 + │ ├── api/ # API调用 + │ ├── assets/ # 资源文件 + │ ├── components/ # 组件 + │ └── views/ # 页面 + └── package.json # 依赖配置 ``` -## 开发说明 +## 安装与部署 + +### 后端部署 + +1. **创建并激活虚拟环境** -### 前端开发 ```bash -cd frontend -npm install -npm run serve +# 创建虚拟环境 +conda create -n calc python=3.10 +conda activate calc ``` -### 后端开发 +2. **安装依赖** + ```bash cd backend pip install -r requirements.txt -python manage.py runserver ``` -## 部署说明 +3. **配置环境变量** -### 使用 SSL 证书 +创建`.env`文件在backend目录下,添加以下内容: -1. 将 SSL 证书文件放在 `nginx/ssl/` 目录下: - - `cert.pem`:SSL 证书文件 - - `key.pem`:SSL 私钥文件 - -2. 修改 `nginx/conf.d/default.conf` 中的 `server_name` 为你的域名。 - -### 环境变量 - -在 `.env` 文件中配置以下环境变量: ``` +# AWS凭证 AWS_ACCESS_KEY_ID=your_access_key AWS_SECRET_ACCESS_KEY=your_secret_key -AWS_DEFAULT_REGION=your_region +AWS_DEFAULT_REGION=us-east-1 + +# MySQL数据库配置 +MYSQL_HOST=localhost +MYSQL_USER=your_username +MYSQL_PASSWORD=your_password +MYSQL_DATABASE=aws_price ``` -## 常见问题 +4. **启动开发服务器** -1. 如果遇到权限问题,请确保使用 `sudo` 运行安装脚本: ```bash -sudo ./install.sh -``` +# 开发环境 +uvicorn main:app --reload --host 0.0.0.0 --port 8000 -2. 如果服务启动失败,可以查看日志: -```bash -docker-compose logs -``` - -3. 如果需要重新构建前端: -```bash -cd frontend -npm run build -``` - -## 维护说明 - -### 更新应用 -```bash -git pull -./install.sh -``` - -### 停止服务 -```bash -docker-compose down -``` - -### 查看日志 -```bash -docker-compose logs -f -``` - -## 安装说明 - -### 开发环境部署 - -#### 后端设置 - -1. 创建虚拟环境: -```bash -python -m venv venv -source venv/bin/activate # Linux/Mac -venv\Scripts\activate # Windows -``` - -2. 安装依赖: -```bash -pip install -r requirements.txt -``` - -3. 配置AWS凭证: -创建`.env`文件并添加以下内容: -``` -AWS_ACCESS_KEY_ID=your_access_key -AWS_SECRET_ACCESS_KEY=your_secret_key -AWS_DEFAULT_REGION=your_region -``` - -4. 运行后端服务: -```bash -cd backend -uvicorn main:app --reload -``` - -#### 前端设置 - -1. 安装依赖: -```bash -cd frontend -npm install -``` - -2. 运行开发服务器: -```bash -npm run serve -``` - -### 生产环境部署 - -#### 后端部署 - -1. 安装生产环境依赖: -```bash -pip install -r requirements.txt -pip install gunicorn -``` - -2. 使用 Gunicorn 启动后端服务: -```bash -cd backend +# 生产环境 gunicorn main:app -w 4 -k uvicorn.workers.UvicornWorker -b 0.0.0.0:8000 ``` -3. 配置 Nginx 反向代理(可选): -```nginx -server { - listen 80; - server_name your_domain.com; +### 前端部署 - location /api { - proxy_pass http://localhost:8000; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - } -} -``` +1. **安装依赖** -#### 前端部署 - -1. 构建生产版本: ```bash cd frontend +npm install +``` + +2. **启动开发服务器** + +```bash +npm run serve +``` + +3. **构建生产版本** + +```bash npm run build ``` -2. 配置 Nginx 服务静态文件: +4. **前端生产环境部署** + +将`frontend/dist`目录下的文件部署到Web服务器的根目录。 + +### Nginx配置示例 + ```nginx server { listen 80; server_name your_domain.com; - root /path/to/frontend/dist; - index index.html; - + # 前端静态文件 location / { + root /path/to/frontend/dist; + index index.html; try_files $uri $uri/ /index.html; } + # 后端API代理 location /api { - proxy_pass http://localhost:8000; + proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } ``` -### Docker 部署 +## API接口说明 -1. 构建后端镜像: -```bash -cd backend -docker build -t aws-calc-backend . +### 主要API端点 + +- `GET /api/regions` - 获取所有可用区域 +- `GET /api/instance-types` - 获取所有实例类型 +- `POST /api/search-instances` - 搜索符合条件的实例(AWS API方式) +- `POST /api/search-instances-v2` - 搜索符合条件的实例(数据库方式) +- `POST /api/compare-prices` - 对比多个配置的价格 + +### 示例请求 + +```json +// 搜索实例示例请求 +POST /api/search-instances-v2 +{ + "cpu_cores": 4, + "memory_gb": 16, + "disk_gb": 100, + "region": "us-east-1", + "operating_system": "Linux" +} ``` -2. 构建前端镜像: -```bash -cd frontend -docker build -t aws-calc-frontend . -``` +## 数据库说明 -3. 使用 Docker Compose 启动服务: -```bash -docker-compose up -d -``` +该应用使用MySQL数据库存储AWS实例价格数据,主要表结构如下: -4. 查看服务状态: -```bash -docker-compose ps -``` +**aws_price表** +- `id` - 唯一标识符 +- `locations` - 区域类型 +- `area_en` - 区域英文名称 +- `area_cn` - 区域中文名称 +- `instance_type` - 实例类型 +- `price` - 小时价格 +- `operating_system` - 操作系统 +- `vcpu` - CPU核心数 +- `memory` - 内存大小(GB) +- `updatetime` - 更新时间 -## 环境变量配置 +## 常见问题解答 -### 后端环境变量 -```env -AWS_ACCESS_KEY_ID=your_access_key -AWS_SECRET_ACCESS_KEY=your_secret_key -AWS_DEFAULT_REGION=your_region -CORS_ORIGINS=http://localhost:8080,https://your_domain.com -ENVIRONMENT=production -``` +1. **价格计算不准确?** + - 确保已配置正确的AWS凭证 + - 价格可能因区域和时间而变化 -### 前端环境变量 -```env -VUE_APP_API_URL=http://localhost:8000 -VUE_APP_ENV=production -``` +2. **无法连接数据库?** + - 检查数据库连接配置 + - 确保MySQL服务已启动 + - 验证用户权限 -## 使用说明 - -1. 访问 http://localhost:8080 打开应用 -2. 使用导航菜单切换不同的功能页面 -3. 在表单中选择所需的配置 -4. 点击计算按钮查看结果 - -## 注意事项 - -- 确保已配置有效的AWS凭证 -- 后端服务默认运行在 http://localhost:8000 -- 前端开发服务器默认运行在 http://localhost:8080 -- 生产环境部署时请确保: - - 使用 HTTPS - - 配置适当的安全头部 - - 启用 CORS 保护 - - 设置适当的缓存策略 - - 配置错误监控和日志记录 +3. **API返回错误?** + - 检查日志获取详细错误信息 + - 验证请求格式是否正确 ## 贡献指南 1. Fork 项目 -2. 创建特性分支 -3. 提交更改 -4. 推送到分支 -5. 创建 Pull Request +2. 创建特性分支 (`git checkout -b feature/amazing-feature`) +3. 提交更改 (`git commit -m 'Add some amazing feature'`) +4. 推送到分支 (`git push origin feature/amazing-feature`) +5. 创建Pull Request ## 许可证 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 deleted file mode 100644 index 268326a..0000000 --- a/backend/Dockerfile +++ /dev/null @@ -1,23 +0,0 @@ -FROM python:3.9-slim - -WORKDIR /app - -# 安装系统依赖 -RUN apt-get update && apt-get install -y \ - curl \ - && rm -rf /var/lib/apt/lists/* - -# 复制依赖文件 -COPY requirements.txt . - -# 安装Python依赖 -RUN pip install --no-cache-dir -r requirements.txt - -# 复制应用代码 -COPY . . - -# 暴露端口 -EXPOSE 8000 - -# 启动命令 -CMD ["gunicorn", "main:app", "-w", "4", "-k", "uvicorn.workers.UvicornWorker", "-b", "0.0.0.0:8000"] \ No newline at end of file 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/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index f9d61cb..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,69 +0,0 @@ -version: '3.8' - -services: - backend: - build: - context: ./backend - dockerfile: Dockerfile - environment: - - DATABASE_URL=postgresql://postgres:postgres@db:5432/calc - - REDIS_URL=redis://redis:6379/0 - depends_on: - - db - - redis - ports: - - "8000:8000" - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:8000/health"] - interval: 30s - timeout: 10s - retries: 3 - restart: unless-stopped - - nginx: - image: nginx:stable-alpine - ports: - - "80:80" - - "443:443" - volumes: - - ./frontend/dist:/usr/share/nginx/html - - ./nginx/conf.d:/etc/nginx/conf.d - - ./nginx/ssl:/etc/nginx/ssl - depends_on: - - backend - healthcheck: - test: ["CMD", "nginx", "-t"] - interval: 30s - timeout: 10s - retries: 3 - restart: unless-stopped - - db: - image: postgres:13-alpine - environment: - - POSTGRES_USER=postgres - - POSTGRES_PASSWORD=postgres - - POSTGRES_DB=calc - volumes: - - postgres_data:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U postgres"] - interval: 10s - timeout: 5s - retries: 5 - restart: unless-stopped - - redis: - image: redis:6-alpine - volumes: - - redis_data:/data - healthcheck: - test: ["CMD", "redis-cli", "ping"] - interval: 10s - timeout: 5s - retries: 5 - restart: unless-stopped - -volumes: - postgres_data: - redis_data: \ No newline at end of file diff --git a/frontend/Dockerfile b/frontend/Dockerfile deleted file mode 100644 index 190e0f9..0000000 --- a/frontend/Dockerfile +++ /dev/null @@ -1,29 +0,0 @@ -# 构建阶段 -FROM node:16-alpine as build-stage - -WORKDIR /app - -# 复制依赖文件 -COPY package*.json ./ - -# 安装依赖 -RUN npm install - -# 复制源代码 -COPY . . - -# 构建应用 -RUN npm run build - -# 生产阶段 -FROM nginx:stable-alpine as production-stage - -# 复制构建产物 -COPY --from=build-stage /app/dist /usr/share/nginx/html - -# 复制nginx配置 -COPY nginx.conf /etc/nginx/conf.d/default.conf - -EXPOSE 80 - -CMD ["nginx", "-g", "daemon off;"] \ 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)