删除docker-compose.yml、后端和前端的Dockerfile,简化项目结构。

This commit is contained in:
wangqifan 2025-04-03 12:52:37 +08:00
parent 0c9cab7901
commit efb053b270
10 changed files with 307 additions and 368 deletions

372
README.md
View File

@ -4,325 +4,213 @@
## 功能特点 ## 功能特点
1. 价格计算器 1. **价格计算器**
- 选择实例类型、区域、操作系统和购买选项 - 选择实例类型、区域、操作系统和购买选项
- 实时计算价格 - 实时计算价格
- 显示详细的价格信息 - 显示详细的价格信息
2. 价格对比 2. **实例搜索**
- 支持多个配置的价格对比 - 根据CPU、内存和其他规格查找最合适的实例
- 表格和图表展示 - 支持从AWS官方API和数据库两种数据源查询
- 导出对比结果 - 显示完整规格和价格信息
3. 预算估算 3. **价格比较**
- 支持自定义和预设使用时长 - 支持多个实例配置的并排比较
- 显示月度成本趋势 - 生成标准化的报价单
- 详细的成本明细 - 支持导出Excel格式报价单
## 技术栈 ## 技术栈
- 前端Vue.js 3 + Element Plus + ECharts - **前端**Vue.js 3 + Element Plus
- 后端Python FastAPI + Boto3 - **后端**Python FastAPI
- 数据源AWS API - **数据源**AWS Pricing API + MySQL数据库
## 系统要求 ## 系统要求
- Docker 20.10+ - Python 3.9+
- Docker Compose 2.0+
- Node.js 14+ - Node.js 14+
- npm 6+ - npm 6+
- MySQL 5.7+
## 快速开始 ## 项目结构
1. 克隆项目并进入项目目录:
```bash
git clone <repository-url>
cd calc
```
2. 创建环境变量文件:
```bash
cp .env.example .env
```
然后编辑 `.env` 文件,填入必要的环境变量。
3. 运行安装脚本:
```bash
chmod +x install.sh
./install.sh
```
安装脚本会自动:
- 检查必要的命令是否已安装
- 创建必要的目录结构
- 构建前端项目
- 检查环境变量配置
- 检查 SSL 证书(如果有)
- 启动所有服务
- 检查服务状态
## 访问应用
安装完成后,可以通过以下地址访问应用:
- 前端界面http://localhost
- 后端 APIhttp://localhost:8000
## 目录结构
``` ```
. .
├── backend/ # 后端服务 ├── backend/ # 后端项目
├── frontend/ # 前端项目 │ ├── app/ # 应用代码
├── nginx/ # Nginx 配置 │ │ ├── api/ # API路由
│ ├── conf.d/ # Nginx 配置文件 │ │ ├── core/ # 核心配置
│ └── ssl/ # SSL 证书目录 │ │ ├── models/ # 数据模型
├── docker-compose.yml │ │ └── services/ # 服务层
├── install.sh │ ├── main.py # 入口文件
└── README.md │ └── requirements.txt # 依赖包
└── frontend/ # 前端项目
├── public/ # 静态资源
├── src/ # 源代码
│ ├── api/ # API调用
│ ├── assets/ # 资源文件
│ ├── components/ # 组件
│ └── views/ # 页面
└── package.json # 依赖配置
``` ```
## 开发说明 ## 安装与部署
### 后端部署
1. **创建并激活虚拟环境**
### 前端开发
```bash ```bash
cd frontend # 创建虚拟环境
npm install conda create -n calc python=3.10
npm run serve conda activate calc
``` ```
### 后端开发 2. **安装依赖**
```bash ```bash
cd backend cd backend
pip install -r requirements.txt 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_ACCESS_KEY_ID=your_access_key
AWS_SECRET_ACCESS_KEY=your_secret_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 ```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 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 { 1. **安装依赖**
proxy_pass http://localhost:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
```
#### 前端部署
1. 构建生产版本:
```bash ```bash
cd frontend cd frontend
npm install
```
2. **启动开发服务器**
```bash
npm run serve
```
3. **构建生产版本**
```bash
npm run build npm run build
``` ```
2. 配置 Nginx 服务静态文件: 4. **前端生产环境部署**
`frontend/dist`目录下的文件部署到Web服务器的根目录。
### Nginx配置示例
```nginx ```nginx
server { server {
listen 80; listen 80;
server_name your_domain.com; server_name your_domain.com;
root /path/to/frontend/dist; # 前端静态文件
index index.html;
location / { location / {
root /path/to/frontend/dist;
index index.html;
try_files $uri $uri/ /index.html; try_files $uri $uri/ /index.html;
} }
# 后端API代理
location /api { location /api {
proxy_pass http://localhost:8000; proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
} }
} }
``` ```
### Docker 部署 ## API接口说明
1. 构建后端镜像: ### 主要API端点
```bash
cd backend - `GET /api/regions` - 获取所有可用区域
docker build -t aws-calc-backend . - `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 启动服务: 该应用使用MySQL数据库存储AWS实例价格数据主要表结构如下
```bash
docker-compose up -d
```
4. 查看服务状态: **aws_price表**
```bash - `id` - 唯一标识符
docker-compose ps - `locations` - 区域类型
``` - `area_en` - 区域英文名称
- `area_cn` - 区域中文名称
- `instance_type` - 实例类型
- `price` - 小时价格
- `operating_system` - 操作系统
- `vcpu` - CPU核心数
- `memory` - 内存大小(GB)
- `updatetime` - 更新时间
## 环境变量配置 ## 常见问题解答
### 后端环境变量 1. **价格计算不准确?**
```env - 确保已配置正确的AWS凭证
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
```
### 前端环境变量 2. **无法连接数据库?**
```env - 检查数据库连接配置
VUE_APP_API_URL=http://localhost:8000 - 确保MySQL服务已启动
VUE_APP_ENV=production - 验证用户权限
```
## 使用说明 3. **API返回错误**
- 检查日志获取详细错误信息
1. 访问 http://localhost:8080 打开应用 - 验证请求格式是否正确
2. 使用导航菜单切换不同的功能页面
3. 在表单中选择所需的配置
4. 点击计算按钮查看结果
## 注意事项
- 确保已配置有效的AWS凭证
- 后端服务默认运行在 http://localhost:8000
- 前端开发服务器默认运行在 http://localhost:8080
- 生产环境部署时请确保:
- 使用 HTTPS
- 配置适当的安全头部
- 启用 CORS 保护
- 设置适当的缓存策略
- 配置错误监控和日志记录
## 贡献指南 ## 贡献指南
1. Fork 项目 1. Fork 项目
2. 创建特性分支 2. 创建特性分支 (`git checkout -b feature/amazing-feature`)
3. 提交更改 3. 提交更改 (`git commit -m 'Add some amazing feature'`)
4. 推送到分支 4. 推送到分支 (`git push origin feature/amazing-feature`)
5. 创建 Pull Request 5. 创建Pull Request
## 许可证 ## 许可证

View File

@ -1,3 +1,9 @@
AWS_ACCESS_KEY_ID=AKIAVIOZF67K6HNCUJ5Y AWS_ACCESS_KEY_ID=AKIAVIOZF67K6HNCUJ5Y
AWS_SECRET_ACCESS_KEY=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 AWS_SECRET_ACCESS_KEY=BQjaaHNm5skCN/3k3r/uNdEG9xb49are+hv5fajK
AWS_DEFAULT_REGION=us-east-1 AWS_DEFAULT_REGION=us-east-1
# MySQL数据库配置
MYSQL_HOST=47.76.209.7
MYSQL_USER=aws_price
MYSQL_PASSWORD=123456
MYSQL_DATABASE=aws_price

View File

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

View File

@ -1,8 +1,9 @@
from fastapi import APIRouter, HTTPException 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.config import AWS_REGION_NAMES, AZURE_REGION_NAMES, ALIYUN_REGION_NAMES
from ..core.instance_data import get_instance_info from ..core.instance_data import get_instance_info
from ..services import calculate_price from ..services import calculate_price
from ..services.aws.pricing_v2 import search_instances_v2
from typing import Dict, List, Any from typing import Dict, List, Any
router = APIRouter(prefix="/api") router = APIRouter(prefix="/api")
@ -100,6 +101,26 @@ async def search_instances(request: InstanceSearchRequest):
print(f"搜索实例时出错: {str(e)}") print(f"搜索实例时出错: {str(e)}")
raise HTTPException(status_code=500, detail=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") @router.post("/compare-prices")
async def compare_prices(comparison: PriceComparison): async def compare_prices(comparison: PriceComparison):
"""比较不同配置的价格""" """比较不同配置的价格"""

View File

@ -19,4 +19,12 @@ class InstanceSearchRequest(BaseModel):
disk_gb: Optional[int] = None disk_gb: Optional[int] = None
region: Optional[str] = None region: Optional[str] = None
operating_system: Optional[str] = "Linux" operating_system: Optional[str] = "Linux"
platform: Optional[str] = "aws" # 新增平台字段默认为AWS 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"

View File

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

View File

@ -6,4 +6,5 @@ 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 requests==2.31.0
httpx==0.25.2 httpx==0.25.2
mysql-connector-python==8.2.0

View File

@ -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:

View File

@ -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;"]

View File

@ -63,7 +63,7 @@ const apiService = {
// 搜索实例 // 搜索实例
searchInstances: async (params) => { searchInstances: async (params) => {
try { try {
const response = await apiClient.post('/api/search-instances', params) const response = await apiClient.post('/api/search-instances-v2', params)
return response.data return response.data
} catch (error) { } catch (error) {
console.error('搜索实例失败:', error) console.error('搜索实例失败:', error)