添加了nasa接口的代理服务
parent
f3f8251f47
commit
de5447c5e5
10
Dockerfile
10
Dockerfile
|
|
@ -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 .
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
10
app/main.py
10
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__)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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}")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue