diff --git a/.env.production b/.env.production index ee878f2..2bde59b 100644 --- a/.env.production +++ b/.env.production @@ -18,6 +18,9 @@ REDIS_MAX_CONNECTIONS=50 # ====================== # Application Configuration # ====================== +# JWT Secret Key (IMPORTANT: Change this in production!) +JWT_SECRET_KEY=your-production-secret-key-change-this-to-random-string + # CORS - Support both internal IP access and external domain # Format: comma-separated list of origins # Examples: @@ -45,6 +48,15 @@ CACHE_TTL_DAYS=3 # ====================== MAX_UPLOAD_SIZE=10485760 +# ====================== +# Proxy Configuration +# ====================== +# HTTP Proxy for accessing NASA JPL Horizons API (optional) +# Format: http://host:port or https://host:port +# Example: HTTP_PROXY=http://192.168.124.203:20171 +HTTP_PROXY=http://192.168.124.203:20171 +HTTPS_PROXY=http://192.168.124.203:20171 + # ====================== # Data Path Configuration # ====================== diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md index fa55613..74ed61a 100644 --- a/DEPLOYMENT.md +++ b/DEPLOYMENT.md @@ -133,6 +133,10 @@ cosmo/ # 修改数据库密码(必须) DATABASE_PASSWORD=your_secure_password_here +# 修改 JWT Secret Key(必须,用于生成和验证 JWT token) +# 使用随机字符串,至少 32 位 +JWT_SECRET_KEY=your-random-secret-key-at-least-32-characters-long + # 修改 CORS 配置(支持内网 IP 和外网域名访问) # 方式 1: 允许所有来源(开发/测试环境) CORS_ORIGINS=* @@ -152,6 +156,13 @@ CORS_ORIGINS=http://192.168.1.100,http://your-domain.com,https://your-domain.com # 如果前后端分离部署在不同服务器,才需要设置完整地址: # VITE_API_BASE_URL=http://your-domain.com/api + +# HTTP 代理配置(用于访问 NASA JPL Horizons API) +# 如果服务器在中国境内无法直接访问 NASA API,需要配置 HTTP 代理 +# 格式:http://host:port 或 https://host:port +# 示例:HTTP_PROXY=http://192.168.124.203:20171 +HTTP_PROXY= +HTTPS_PROXY= ``` **重要说明**: diff --git a/PROXY_SETUP.md b/PROXY_SETUP.md new file mode 100644 index 0000000..7877bda --- /dev/null +++ b/PROXY_SETUP.md @@ -0,0 +1,163 @@ +# HTTP 代理配置指南 + +## 问题背景 + +在中国境内部署 Cosmo 时,后端服务需要访问 NASA JPL Horizons API 来获取天体数据,但由于网络限制,直接访问可能会超时或失败。 + +## 解决方案 + +配置 HTTP 代理来访问 NASA API。 + +## 配置步骤 + +### 1. 在 `.env.production` 中配置代理 + +编辑 `/Users/jiliu/WorkSpace/cosmo/.env.production` 文件,添加代理配置: + +```bash +# HTTP 代理配置(用于访问 NASA JPL Horizons API) +# 格式:http://host:port +HTTP_PROXY=http://192.168.124.203:20171 +HTTPS_PROXY=http://192.168.124.203:20171 +``` + +**注意**: +- 代理地址格式必须包含协议前缀(`http://` 或 `https://`) +- 如果您的代理服务器只监听 HTTP,两个配置都使用 `http://` +- 确保代理服务器在部署的服务器上可以访问(192.168.124.203:20171) + +### 2. 重启后端服务 + +配置修改后需要重启后端服务才能生效: + +```bash +# 在部署服务器上执行 +cd /path/to/cosmo +./deploy.sh --restart +``` + +或者使用 Docker Compose: + +```bash +docker-compose restart backend +``` + +## 工作原理 + +### 1. httpx 代理配置 + +后端使用 `httpx` 库直接访问 NASA API(`get_object_data_raw` 方法)。代理通过 `httpx.AsyncClient` 的 `proxies` 参数传递: + +```python +client_kwargs = {"timeout": 5.0} +if settings.proxy_dict: + client_kwargs["proxies"] = settings.proxy_dict + # proxies = {"http://": "http://192.168.124.203:20171", + # "https://": "http://192.168.124.203:20171"} + +async with httpx.AsyncClient(**client_kwargs) as client: + response = await client.get(url, params=params) +``` + +### 2. astroquery 代理配置 + +`astroquery` 库(用于 `get_body_positions` 方法)通过标准环境变量使用代理: + +```python +if settings.http_proxy: + os.environ['HTTP_PROXY'] = settings.http_proxy +if settings.https_proxy: + os.environ['HTTPS_PROXY'] = settings.https_proxy +``` + +## 验证配置 + +### 1. 检查日志 + +配置代理后,启动服务时会在日志中看到: + +``` +INFO - Set HTTP_PROXY for astroquery: http://192.168.124.203:20171 +INFO - Set HTTPS_PROXY for astroquery: http://192.168.124.203:20171 +``` + +访问 NASA API 时会看到: + +``` +INFO - Using proxy for NASA API: {'http://': 'http://192.168.124.203:20171', 'https://': 'http://192.168.124.203:20171'} +INFO - Fetching raw data for body 10 +``` + +### 2. 测试 API 访问 + +在前端点击天体的 "JPL Horizons" 按钮,查看是否能成功获取数据。 + +或者直接访问后端 API: + +```bash +curl http://your-server-ip/api/nasa/object/10 +``` + +## 常见问题 + +### 1. 代理地址格式错误 + +❌ **错误**: +```bash +HTTP_PROXY=192.168.124.203:20171 # 缺少协议前缀 +``` + +✅ **正确**: +```bash +HTTP_PROXY=http://192.168.124.203:20171 +``` + +### 2. 代理服务器无法访问 + +确保代理服务器在部署服务器上可以访问: + +```bash +# 在部署服务器上测试 +curl -x http://192.168.124.203:20171 https://ssd.jpl.nasa.gov/api/horizons.api +``` + +### 3. 仍然超时 + +如果配置代理后仍然超时,检查: + +1. 代理服务器是否运行正常 +2. 代理服务器是否允许访问 `ssd.jpl.nasa.gov` +3. 防火墙是否阻止了连接 + +### 4. 日志中没有代理信息 + +如果日志中没有 "Using proxy" 信息,检查: + +1. `.env.production` 文件是否正确配置 +2. 是否重启了服务 +3. Docker 容器是否正确加载了环境变量: + +```bash +docker-compose exec backend env | grep PROXY +``` + +## 不需要代理的情况 + +如果您的服务器可以直接访问 NASA API(例如:海外服务器),只需将代理配置留空即可: + +```bash +HTTP_PROXY= +HTTPS_PROXY= +``` + +## 相关文件 + +- **配置**: `backend/app/config.py` - Settings 类中的 `http_proxy`, `https_proxy`, `proxy_dict` +- **使用**: `backend/app/services/horizons.py` - HorizonsService 类 +- **环境变量**: `.env.production` - HTTP_PROXY, HTTPS_PROXY + +## 参考链接 + +- [httpx Proxies Documentation](https://www.python-httpx.org/advanced/#http-proxying) +- [NASA JPL Horizons API](https://ssd-api.jpl.nasa.gov/doc/horizons.html) +- [astroquery Documentation](https://astroquery.readthedocs.io/) diff --git a/backend/app/config.py b/backend/app/config.py index 5cbcb68..7f1358e 100644 --- a/backend/app/config.py +++ b/backend/app/config.py @@ -64,6 +64,22 @@ class Settings(BaseSettings): upload_dir: str = "upload" max_upload_size: int = 10485760 # 10MB + # Proxy settings (for accessing NASA JPL Horizons API in China) + http_proxy: str = "" + https_proxy: str = "" + + @property + def proxy_dict(self) -> dict[str, str] | None: + """Get proxy configuration as a dictionary for httpx""" + if self.http_proxy or self.https_proxy: + proxies = {} + if self.http_proxy: + proxies["http://"] = self.http_proxy + if self.https_proxy: + proxies["https://"] = self.https_proxy + return proxies + return None + @property def database_url(self) -> str: """Construct database URL for SQLAlchemy""" diff --git a/backend/app/main.py b/backend/app/main.py index ac1ab9a..687eab9 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -34,11 +34,19 @@ from app.services.cache_preheat import preheat_all_caches from app.database import close_db # Configure logging +# Set root logger to WARNING in production, INFO in development +log_level = logging.INFO if settings.jwt_secret_key == "your-secret-key-change-this-in-production" else logging.WARNING logging.basicConfig( - level=logging.INFO, + level=log_level, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", ) +# Reduce noise from specific loggers in production +if log_level == logging.WARNING: + logging.getLogger("app.services.cache").setLevel(logging.ERROR) + logging.getLogger("app.services.redis_cache").setLevel(logging.ERROR) + logging.getLogger("app.api.celestial_position").setLevel(logging.WARNING) + logger = logging.getLogger(__name__) diff --git a/backend/app/services/horizons.py b/backend/app/services/horizons.py index 3ed3d6e..ed678be 100644 --- a/backend/app/services/horizons.py +++ b/backend/app/services/horizons.py @@ -7,8 +7,10 @@ from astropy.time import Time import logging import re import httpx +import os from app.models.celestial import Position, CelestialBody +from app.config import settings logger = logging.getLogger(__name__) @@ -20,6 +22,15 @@ class HorizonsService: """Initialize the service""" self.location = "@sun" # Heliocentric coordinates + # Set proxy for astroquery if configured + # astroquery uses standard HTTP_PROXY and HTTPS_PROXY environment variables + if settings.http_proxy: + os.environ['HTTP_PROXY'] = settings.http_proxy + logger.info(f"Set HTTP_PROXY for astroquery: {settings.http_proxy}") + if settings.https_proxy: + os.environ['HTTPS_PROXY'] = settings.https_proxy + logger.info(f"Set HTTPS_PROXY for astroquery: {settings.https_proxy}") + async def get_object_data_raw(self, body_id: str) -> str: """ Get raw object data (terminal style text) from Horizons @@ -33,7 +44,7 @@ class HorizonsService: url = "https://ssd.jpl.nasa.gov/api/horizons.api" # Ensure ID is quoted for COMMAND cmd_val = f"'{body_id}'" if not body_id.startswith("'") else body_id - + params = { "format": "text", "COMMAND": cmd_val, @@ -44,10 +55,15 @@ class HorizonsService: } try: - async with httpx.AsyncClient() as client: + # Configure proxy if available + client_kwargs = {"timeout": 5.0} + if settings.proxy_dict: + client_kwargs["proxies"] = settings.proxy_dict + logger.info(f"Using proxy for NASA API: {settings.proxy_dict}") + + async with httpx.AsyncClient(**client_kwargs) as client: logger.info(f"Fetching raw data for body {body_id}") - # Reduced timeout for China network (NASA JPL may be blocked) - response = await client.get(url, params=params, timeout=5.0) + response = await client.get(url, params=params) if response.status_code != 200: raise Exception(f"NASA API returned status {response.status_code}")