本文档整理了 Real Address Generator API 项目的优化建议,按优先级和类别分类。
当前问题:
# requirements.txt
fastapi
uvicorn
requests
Faker
babel
phonenumbers
所有依赖都没有指定版本号,这可能导致:
- 在不同环境中安装不同版本
- 新版本 breaking changes 可能导致应用崩溃
- 难以复现生产环境问题
建议修复:
fastapi>=0.100.0,<1.0.0
uvicorn>=0.22.0,<1.0.0
requests>=2.28.0,<3.0.0
Faker>=18.0.0,<50.0.0
babel>=2.12.0,<3.0.0
phonenumbers>=8.13.0,<10.0.0
httpx>=0.24.0 # 用于测试和异步请求
或者使用 pip freeze > requirements.txt 锁定精确版本。
当前问题:
# address_fetcher.py
def _wait_for_rate_limit(self):
current_time = time.time()
elapsed = current_time - self.last_request_time
if elapsed < 1.1:
time.sleep(1.1 - elapsed)
self.last_request_time = time.time()这个实现在多线程/异步环境下不安全,可能导致:
- 多个请求同时通过速率检查
- 违反 Nominatim 的 1 请求/秒限制
- 被 Nominatim 封禁
建议修复:
import threading
class AddressFetcher:
def __init__(self):
self._rate_limit_lock = threading.Lock()
self.last_request_time = 0
# ...
def _wait_for_rate_limit(self):
with self._rate_limit_lock:
current_time = time.time()
elapsed = current_time - self.last_request_time
if elapsed < 1.1:
sleep_time = 1.1 - elapsed
time.sleep(sleep_time)
self.last_request_time = time.time()当前问题:
- 缺少健康检查端点,不利于容器编排和负载均衡
建议添加:
# main.py
@app.get("/health")
def health_check():
return {"status": "healthy"}
@app.get("/ready")
def readiness_check():
# 可以添加依赖服务检查
return {"status": "ready"}当前问题:
resp = requests.get(self.nominatim_url, params=params, ...)使用同步 requests 库会阻塞整个请求处理,降低并发性能。
建议修复:
使用 httpx 替代 requests 实现异步请求:
import httpx
class AddressFetcher:
async def _query_nominatim_async(self, ...):
async with httpx.AsyncClient() as client:
resp = await client.get(
self.nominatim_url,
params=params,
headers=self._get_headers(),
timeout=25.0
)
# ...注意: 这需要将主 API 处理函数也改为异步,是较大的重构。
当前问题:
- 每次请求都查询外部 API
- 相同国家/城市的重复请求造成资源浪费
- 增加响应延迟
建议方案:
方案 A: 简单内存缓存
from functools import lru_cache
from datetime import datetime, timedelta
class AddressFetcher:
def __init__(self):
self._cache = {}
self._cache_ttl = timedelta(hours=1)
def _get_from_cache(self, key):
if key in self._cache:
data, timestamp = self._cache[key]
if datetime.now() - timestamp < self._cache_ttl:
return data
del self._cache[key]
return None方案 B: 使用 Redis (生产环境推荐)
import redis
class AddressFetcher:
def __init__(self):
self.redis_client = redis.Redis(
host=os.getenv("REDIS_HOST", "localhost"),
port=int(os.getenv("REDIS_PORT", 6379))
)当前问题:
FROM python:3.12-slim
# ...
COPY . .- 未使用多阶段构建
- 复制了不必要的文件 (.git, tests 等)
建议优化:
# Build stage
FROM python:3.12-slim AS builder
WORKDIR /app
# Install dependencies
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt
# Final stage
FROM python:3.12-slim
WORKDIR /app
# Copy only necessary files
COPY --from=builder /root/.local /root/.local
COPY app/ ./app/
ENV PATH=/root/.local/bin:$PATH
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
ENV PORT=8000
EXPOSE $PORT
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:$PORT/health || exit 1
CMD ["sh", "-c", "uvicorn app.main:app --host 0.0.0.0 --port $PORT"]添加 .dockerignore:
.git
.gitignore
tests/
*.md
*.pyc
__pycache__
.pytest_cache
.env
当前问题:
- 配置分散在代码中
- 使用
os.getenv()直接获取,缺少验证
建议方案: 使用 Pydantic Settings:
# app/config.py
# 注意: 需要在 requirements.txt 中添加 pydantic-settings (Pydantic v2 分离的包)
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8"
)
# Nominatim 配置
nominatim_email: str = "admin@realaddressgenerator.com"
nominatim_user_agent: str = ""
nominatim_rate_limit: float = 1.1
# 服务配置
port: int = 8000
log_level: str = "INFO"
# 缓存配置
cache_enabled: bool = False
cache_ttl_hours: int = 1
@property
def effective_user_agent(self) -> str:
if self.nominatim_user_agent:
return self.nominatim_user_agent
return f"RealAddressGenerator/1.0 ({self.nominatim_email})"
settings = Settings()当前问题:
- 返回类型不明确
- 字典结构无类型定义
建议改进:
from typing import TypedDict, Optional
class AddressData(TypedDict):
address: str
city: Optional[str]
state: Optional[str]
zipcode: Optional[str]
country: Optional[str]
full_address: str
google_maps_url: Optional[str]
class PersonaData(TypedDict):
name: str
phone: str
def _parse_osm_result(self, result: dict) -> AddressData:
# ...当前问题:
logging.basicConfig(level=logging.INFO)- 日志格式不统一
- 缺少结构化日志
- 生产环境日志级别固定
建议改进:
import logging
import json
from datetime import datetime
class StructuredFormatter(logging.Formatter):
def format(self, record):
log_entry = {
"timestamp": datetime.utcnow().isoformat(),
"level": record.levelname,
"logger": record.name,
"message": record.getMessage(),
}
if record.exc_info:
log_entry["exception"] = self.formatException(record.exc_info)
return json.dumps(log_entry)
def setup_logging(level: str = "INFO"):
handler = logging.StreamHandler()
handler.setFormatter(StructuredFormatter())
logging.root.handlers = [handler]
logging.root.setLevel(getattr(logging, level.upper()))当前问题:
except Exception as e:
logger.error(f"Nominatim Request Error: {e}")- 捕获所有异常过于宽泛
- 缺少重试机制
建议改进:
import requests
from requests.exceptions import Timeout, ConnectionError
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type
class AddressFetcher:
@retry(
stop=stop_after_attempt(3),
wait=wait_exponential(multiplier=1, min=2, max=10),
retry=retry_if_exception_type((Timeout, ConnectionError)),
reraise=True
)
def _query_nominatim_with_retry(self, ...):
try:
resp = requests.get(...)
resp.raise_for_status()
return resp.json()
except Timeout:
logger.warning("Nominatim request timed out")
raise
except ConnectionError:
logger.warning("Failed to connect to Nominatim")
raise
except requests.HTTPError as e:
if e.response.status_code == 429:
logger.warning("Rate limited by Nominatim")
raise当前问题:
test_address_fetcher.py使用真实 API 调用- 缺少异步测试支持
- 测试覆盖率未知
建议改进:
- 添加
pytest-cov进行覆盖率统计 - 添加
pytest-asyncio支持异步测试 - 所有测试使用 Mock,避免真实 API 调用
- 添加 CI/CD 集成
# .github/workflows/test.yml
name: Test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: pip install -r requirements.txt pytest pytest-cov
- run: pytest --cov=app --cov-report=xml tests/当前问题:
- FastAPI 自动文档基础信息不完整
- 缺少示例响应
建议改进:
app = FastAPI(
title="Real Address Generator API",
description="""
## 功能
生成真实存在的物理地址,配合本地化的姓名和电话号码。
## 特性
- 🌍 支持全球多个国家
- 🔄 智能回退机制
- 🌐 多语言输入支持
""",
version="1.0.0",
contact={
"name": "API Support",
"email": "support@realaddressgenerator.com",
},
license_info={
"name": "MIT",
},
)
@app.get(
"/api/generate",
response_model=AddressResponse,
responses={
200: {
"description": "成功生成地址",
"content": {
"application/json": {
"example": {
"name": "John Doe",
"phone": "+1 201 555 0123",
"address": "44 West 63rd Street",
"city_state": "New York, New York",
"zipcode": "10023",
"country": "United States",
"full_address": "..."
}
}
}
},
503: {"description": "服务暂时不可用"}
}
)
def generate_address(...):
...| 优化项 | 影响 | 实施难度 | 优先级 |
|---|---|---|---|
| 依赖版本锁定 | 高 | 低 | 🔴 高 |
| 速率限制并发安全 | 高 | 低 | 🔴 高 |
| 健康检查端点 | 中 | 低 | 🔴 高 |
| 异步 HTTP | 高 | 高 | 🟡 中 |
| 缓存机制 | 高 | 中 | 🟡 中 |
| Docker 优化 | 中 | 低 | 🟡 中 |
| 配置管理 | 中 | 中 | 🟡 中 |
| 类型提示 | 低 | 低 | 🟢 低 |
| 日志增强 | 中 | 中 | 🟢 低 |
| 错误处理 | 中 | 中 | 🟢 低 |
| 测试改进 | 中 | 中 | 🟢 低 |
| API 文档 | 低 | 低 | 🟢 低 |
第一阶段 (1-2 天):
- ✅ 锁定依赖版本
- ✅ 修复速率限制器并发问题
- ✅ 添加健康检查端点
第二阶段 (3-5 天): 4. 添加简单内存缓存 5. 优化 Docker 构建 6. 改进配置管理
第三阶段 (1-2 周): 7. 重构为异步架构 8. 增强日志和错误处理 9. 完善测试和 CI/CD
如有其他优化建议,欢迎提交 Issue 或 PR。