添加了nasa接口的代理服务

main
mula.liu 2025-12-03 13:33:25 +08:00
parent f3f8251f47
commit de5447c5e5
8 changed files with 104 additions and 18 deletions

View File

@ -1,9 +1,13 @@
# Backend Dockerfile for Cosmo
FROM python:3.12-slim
FROM swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/python:3.12-slim
# Set working directory
WORKDIR /app
# Configure Debian mirrors (Aliyun)
RUN sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list.d/debian.sources && \
sed -i 's/security.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list.d/debian.sources
# Install system dependencies
RUN apt-get update && apt-get install -y \
gcc \
@ -11,6 +15,10 @@ RUN apt-get update && apt-get install -y \
curl \
&& rm -rf /var/lib/apt/lists/*
# Configure pip to use Aliyun mirror
RUN pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/ && \
pip config set install.trusted-host mirrors.aliyun.com
# Copy requirements first for better caching
COPY requirements.txt .

View File

@ -1,19 +1,40 @@
"""
Application configuration
"""
from pydantic_settings import BaseSettings
from pydantic import Field
from typing import Union, Any
from pydantic_settings import BaseSettings, SettingsConfigDict
from pydantic import Field, field_validator, ValidationInfo
class Settings(BaseSettings):
"""Application settings"""
model_config = SettingsConfigDict(
env_file=".env",
# Don't try to parse json for environment variables
env_parse_none_str=None,
)
# Application
app_name: str = "Cosmo - Deep Space Explorer"
api_prefix: str = "/api"
# CORS settings - allow all origins for development (IP access support)
cors_origins: list[str] = ["*"]
# CORS settings - stored as string in env, converted to list
cors_origins: Union[str, list[str]] = "*"
@field_validator('cors_origins', mode='before')
@classmethod
def validate_cors_origins(cls, v: Any) -> list[str]:
"""Parse CORS origins from comma-separated string or JSON array"""
if v is None:
return ["*"]
if isinstance(v, str):
# Parse comma-separated string
origins = [origin.strip() for origin in v.split(',') if origin.strip()]
return origins if origins else ["*"]
if isinstance(v, list):
return v
return ["*"]
# Cache settings
cache_ttl_days: int = 3
@ -43,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"""
@ -58,8 +95,5 @@ class Settings(BaseSettings):
return f"redis://:{self.redis_password}@{self.redis_host}:{self.redis_port}/{self.redis_db}"
return f"redis://{self.redis_host}:{self.redis_port}/{self.redis_db}"
class Config:
env_file = ".env"
settings = Settings()

View File

@ -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__)

View File

@ -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,9 +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}")
response = await client.get(url, params=params, timeout=30.0)
response = await client.get(url, params=params)
if response.status_code != 200:
raise Exception(f"NASA API returned status {response.status_code}")

View File

@ -211,8 +211,16 @@ class SystemSettingsService:
for default in defaults:
existing = await self.get_setting(default["key"], session)
if not existing:
await self.create_setting(default, session)
logger.info(f"Created default setting: {default['key']}")
try:
await self.create_setting(default, session)
logger.info(f"Created default setting: {default['key']}")
except Exception as e:
# Ignore duplicate key errors (race condition between workers)
if "duplicate key" in str(e).lower() or "unique constraint" in str(e).lower():
logger.debug(f"Setting {default['key']} already exists (created by another worker)")
else:
logger.error(f"Error creating default setting {default['key']}: {e}")
raise
# Singleton instance

9
pip.conf 100644
View File

@ -0,0 +1,9 @@
# pip configuration for Aliyun mirror
# This file speeds up pip install in development
[global]
index-url = https://mirrors.aliyun.com/pypi/simple/
trusted-host = mirrors.aliyun.com
[install]
trusted-host = mirrors.aliyun.com

View File

@ -4,6 +4,7 @@ astroquery==0.4.7
astropy==6.0.0
pydantic==2.5.0
pydantic-settings==2.1.0
email-validator==2.1.0
python-dotenv==1.0.0
httpx==0.25.2

View File

@ -12,7 +12,7 @@
Target Server Version : 140008 (140008)
File Encoding : 65001
Date: 02/12/2025 16:02:50
Date: 02/12/2025 23:57:44
*/
@ -5350,6 +5350,7 @@ INSERT INTO "public"."system_settings" ("id", "key", "value", "value_type", "cat
INSERT INTO "public"."system_settings" ("id", "key", "value", "value_type", "category", "label", "description", "is_public", "created_at", "updated_at") VALUES (6, 'orbit_points', '200', 'int', 'visualization', '轨道线点数', '生成轨道线时使用的点数,越多越平滑但性能越低', 't', '2025-11-30 05:10:43.416174+00', NULL);
INSERT INTO "public"."system_settings" ("id", "key", "value", "value_type", "category", "label", "description", "is_public", "created_at", "updated_at") VALUES (1, 'timeline_interval_days', '30', 'int', 'visualization', '时间轴播放间隔(天)', '星图时间轴播放时每次跳转的天数间隔', 't', '2025-11-30 05:10:43.416174+00', '2025-11-30 15:21:56.563851+00');
INSERT INTO "public"."system_settings" ("id", "key", "value", "value_type", "category", "label", "description", "is_public", "created_at", "updated_at") VALUES (7, 'danmaku_ttl', '86400', 'int', 'platform', '弹幕保留时间', '用户发送的弹幕在系统中保留的时间(秒)', 't', '2025-11-30 16:12:15.94262+00', NULL);
INSERT INTO "public"."system_settings" ("id", "key", "value", "value_type", "category", "label", "description", "is_public", "created_at", "updated_at") VALUES (8, 'default_password', 'cosmo', 'string', 'general', '初始化密码', '重置的默认密码', 'f', '2025-12-02 10:35:08.508068+00', NULL);
COMMIT;
-- ----------------------------
@ -5434,8 +5435,8 @@ COMMENT ON COLUMN "public"."users"."last_login_at" IS 'Last login time';
-- Records of users
-- ----------------------------
BEGIN;
INSERT INTO "public"."users" ("id", "username", "password_hash", "email", "full_name", "is_active", "created_at", "updated_at", "last_login_at") VALUES (3, 'pepperdog', '$2b$12$KqTNQKf13L5rDUkt6yabNuh7fbmRsxME3W5OoVXj/C2.CLNcHvAz.', 'mula.liu@163.com', '胡椒狗', 't', '2025-11-29 18:30:52.045915', '2025-12-01 09:22:53.233616', '2025-12-01 09:22:53.516333');
INSERT INTO "public"."users" ("id", "username", "password_hash", "email", "full_name", "is_active", "created_at", "updated_at", "last_login_at") VALUES (2, 'cosmo', '$2b$12$42d8/NAaYJlK8w/1yBd5uegdHlDkpC9XFtXYu2sWq0EXj48KAMZ0i', 'admin@cosmo.com', 'COSMO零号', 't', '2025-11-28 18:07:11.767382', '2025-12-02 03:27:52.709283', '2025-12-02 03:27:52.961435');
INSERT INTO "public"."users" ("id", "username", "password_hash", "email", "full_name", "is_active", "created_at", "updated_at", "last_login_at") VALUES (3, 'pepperdog', '$2b$12$zgLRor/cbpt0TF2aqeuuDupeXRRXrtBL57rFgfICzPjRYync6zYIq', 'mula.liu@163.com', '胡椒狗', 't', '2025-11-29 18:30:52.045915', '2025-12-02 10:42:10.298484', '2025-12-02 10:42:10.556614');
INSERT INTO "public"."users" ("id", "username", "password_hash", "email", "full_name", "is_active", "created_at", "updated_at", "last_login_at") VALUES (2, 'cosmo', '$2b$12$42d8/NAaYJlK8w/1yBd5uegdHlDkpC9XFtXYu2sWq0EXj48KAMZ0i', 'admin@cosmo.com', 'COSMO零号', 't', '2025-11-28 18:07:11.767382', '2025-12-02 10:42:52.277596', '2025-12-02 10:42:52.561184');
COMMIT;
-- ----------------------------
@ -5492,7 +5493,7 @@ SELECT setval('"public"."static_data_id_seq"', 64, true);
-- ----------------------------
ALTER SEQUENCE "public"."system_settings_id_seq"
OWNED BY "public"."system_settings"."id";
SELECT setval('"public"."system_settings_id_seq"', 7, true);
SELECT setval('"public"."system_settings_id_seq"', 8, true);
-- ----------------------------
-- Alter sequences owned by