添加了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 # 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 # Set working directory
WORKDIR /app 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 # Install system dependencies
RUN apt-get update && apt-get install -y \ RUN apt-get update && apt-get install -y \
gcc \ gcc \
@ -11,6 +15,10 @@ RUN apt-get update && apt-get install -y \
curl \ curl \
&& rm -rf /var/lib/apt/lists/* && 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 first for better caching
COPY requirements.txt . COPY requirements.txt .

View File

@ -1,19 +1,40 @@
""" """
Application configuration Application configuration
""" """
from pydantic_settings import BaseSettings from typing import Union, Any
from pydantic import Field from pydantic_settings import BaseSettings, SettingsConfigDict
from pydantic import Field, field_validator, ValidationInfo
class Settings(BaseSettings): class Settings(BaseSettings):
"""Application settings""" """Application settings"""
model_config = SettingsConfigDict(
env_file=".env",
# Don't try to parse json for environment variables
env_parse_none_str=None,
)
# Application # Application
app_name: str = "Cosmo - Deep Space Explorer" app_name: str = "Cosmo - Deep Space Explorer"
api_prefix: str = "/api" api_prefix: str = "/api"
# CORS settings - allow all origins for development (IP access support) # CORS settings - stored as string in env, converted to list
cors_origins: list[str] = ["*"] 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 settings
cache_ttl_days: int = 3 cache_ttl_days: int = 3
@ -43,6 +64,22 @@ class Settings(BaseSettings):
upload_dir: str = "upload" upload_dir: str = "upload"
max_upload_size: int = 10485760 # 10MB 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 @property
def database_url(self) -> str: def database_url(self) -> str:
"""Construct database URL for SQLAlchemy""" """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_password}@{self.redis_host}:{self.redis_port}/{self.redis_db}"
return f"redis://{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() settings = Settings()

View File

@ -34,11 +34,19 @@ from app.services.cache_preheat import preheat_all_caches
from app.database import close_db from app.database import close_db
# Configure logging # 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( logging.basicConfig(
level=logging.INFO, level=log_level,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", 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__) logger = logging.getLogger(__name__)

View File

@ -7,8 +7,10 @@ from astropy.time import Time
import logging import logging
import re import re
import httpx import httpx
import os
from app.models.celestial import Position, CelestialBody from app.models.celestial import Position, CelestialBody
from app.config import settings
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -20,6 +22,15 @@ class HorizonsService:
"""Initialize the service""" """Initialize the service"""
self.location = "@sun" # Heliocentric coordinates 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: async def get_object_data_raw(self, body_id: str) -> str:
""" """
Get raw object data (terminal style text) from Horizons Get raw object data (terminal style text) from Horizons
@ -33,7 +44,7 @@ class HorizonsService:
url = "https://ssd.jpl.nasa.gov/api/horizons.api" url = "https://ssd.jpl.nasa.gov/api/horizons.api"
# Ensure ID is quoted for COMMAND # Ensure ID is quoted for COMMAND
cmd_val = f"'{body_id}'" if not body_id.startswith("'") else body_id cmd_val = f"'{body_id}'" if not body_id.startswith("'") else body_id
params = { params = {
"format": "text", "format": "text",
"COMMAND": cmd_val, "COMMAND": cmd_val,
@ -44,9 +55,15 @@ class HorizonsService:
} }
try: 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}") 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: if response.status_code != 200:
raise Exception(f"NASA API returned status {response.status_code}") raise Exception(f"NASA API returned status {response.status_code}")

View File

@ -211,8 +211,16 @@ class SystemSettingsService:
for default in defaults: for default in defaults:
existing = await self.get_setting(default["key"], session) existing = await self.get_setting(default["key"], session)
if not existing: if not existing:
await self.create_setting(default, session) try:
logger.info(f"Created default setting: {default['key']}") 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 # 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 astropy==6.0.0
pydantic==2.5.0 pydantic==2.5.0
pydantic-settings==2.1.0 pydantic-settings==2.1.0
email-validator==2.1.0
python-dotenv==1.0.0 python-dotenv==1.0.0
httpx==0.25.2 httpx==0.25.2

View File

@ -12,7 +12,7 @@
Target Server Version : 140008 (140008) Target Server Version : 140008 (140008)
File Encoding : 65001 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 (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 (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 (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; COMMIT;
-- ---------------------------- -- ----------------------------
@ -5434,8 +5435,8 @@ COMMENT ON COLUMN "public"."users"."last_login_at" IS 'Last login time';
-- Records of users -- Records of users
-- ---------------------------- -- ----------------------------
BEGIN; 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 (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 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 (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; COMMIT;
-- ---------------------------- -- ----------------------------
@ -5492,7 +5493,7 @@ SELECT setval('"public"."static_data_id_seq"', 64, true);
-- ---------------------------- -- ----------------------------
ALTER SEQUENCE "public"."system_settings_id_seq" ALTER SEQUENCE "public"."system_settings_id_seq"
OWNED BY "public"."system_settings"."id"; 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 -- Alter sequences owned by