2026-03-27 13:04:11 +00:00
|
|
|
from datetime import datetime, timedelta, timezone
|
2026-03-27 10:19:34 +00:00
|
|
|
from pathlib import Path
|
|
|
|
|
from typing import Literal
|
|
|
|
|
|
|
|
|
|
from pydantic import Field
|
|
|
|
|
from pydantic_settings import BaseSettings
|
|
|
|
|
|
2026-03-27 13:04:11 +00:00
|
|
|
CST = timezone(timedelta(hours=8))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def now_cst() -> datetime:
|
|
|
|
|
"""Return current time in CST (UTC+8) as naive datetime for SQLite."""
|
|
|
|
|
return datetime.now(CST).replace(tzinfo=None)
|
|
|
|
|
|
2026-03-27 10:19:34 +00:00
|
|
|
# Project root directory (where config.py lives → parent = app/ → parent = project root)
|
|
|
|
|
_PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
|
|
|
|
_DEFAULT_DB_PATH = _PROJECT_ROOT / "badge_admin.db"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Settings(BaseSettings):
|
|
|
|
|
APP_NAME: str = "KKS Badge Management System"
|
|
|
|
|
DATABASE_URL: str = Field(
|
|
|
|
|
default=f"sqlite+aiosqlite:///{_DEFAULT_DB_PATH}",
|
|
|
|
|
description="Database connection URL (absolute path for SQLite)",
|
|
|
|
|
)
|
|
|
|
|
TCP_HOST: str = "0.0.0.0"
|
|
|
|
|
TCP_PORT: int = Field(default=5000, ge=1, le=65535)
|
|
|
|
|
API_HOST: str = "0.0.0.0"
|
|
|
|
|
API_PORT: int = Field(default=8088, ge=1, le=65535)
|
|
|
|
|
DEBUG: bool = Field(default=False, description="Enable debug mode (SQL echo, verbose errors)")
|
|
|
|
|
|
|
|
|
|
# API authentication
|
|
|
|
|
API_KEY: str | None = Field(default=None, description="API key for authentication (None=disabled)")
|
|
|
|
|
CORS_ORIGINS: str = Field(default="*", description="Comma-separated allowed CORS origins")
|
|
|
|
|
|
|
|
|
|
# Rate limiting
|
|
|
|
|
RATE_LIMIT_DEFAULT: str = Field(default="60/minute", description="Default rate limit")
|
|
|
|
|
RATE_LIMIT_WRITE: str = Field(default="30/minute", description="Rate limit for write operations")
|
|
|
|
|
|
|
|
|
|
# 高德地图 API (geocoding)
|
2026-03-27 13:04:11 +00:00
|
|
|
AMAP_KEY: str | None = Field(default=None, description="高德地图 Web服务 key (逆地理编码/POI搜索)")
|
|
|
|
|
AMAP_SECRET: str | None = Field(default=None, description="高德地图 Web服务安全密钥")
|
|
|
|
|
AMAP_HARDWARE_KEY: str | None = Field(default=None, description="高德地图智能硬件定位 key (基站/WiFi定位)")
|
|
|
|
|
AMAP_HARDWARE_SECRET: str | None = Field(default=None, description="高德地图智能硬件定位安全密钥 (与 HARDWARE_KEY 配对)")
|
2026-03-27 10:19:34 +00:00
|
|
|
|
|
|
|
|
# Geocoding
|
|
|
|
|
GEOCODING_DEFAULT_IMEI: str = Field(default="868120334031363", description="Default IMEI for AMAP geocoding API")
|
|
|
|
|
GEOCODING_CACHE_SIZE: int = Field(default=10000, description="Max geocoding cache entries")
|
|
|
|
|
|
|
|
|
|
# Track query limit
|
|
|
|
|
TRACK_MAX_POINTS: int = Field(default=10000, description="Maximum points returned by track endpoint")
|
|
|
|
|
|
2026-03-27 13:04:11 +00:00
|
|
|
# Fence auto-attendance
|
|
|
|
|
FENCE_CHECK_ENABLED: bool = Field(default=True, description="Enable automatic fence attendance check on location report")
|
|
|
|
|
FENCE_LBS_TOLERANCE_METERS: int = Field(default=200, description="Extra tolerance (meters) for LBS locations in fence check")
|
|
|
|
|
FENCE_WIFI_TOLERANCE_METERS: int = Field(default=100, description="Extra tolerance (meters) for WiFi locations in fence check")
|
|
|
|
|
FENCE_MIN_INSIDE_SECONDS: int = Field(default=60, description="Minimum seconds between fence attendance transitions (debounce)")
|
|
|
|
|
|
2026-03-27 10:19:34 +00:00
|
|
|
# Data retention
|
|
|
|
|
DATA_RETENTION_DAYS: int = Field(default=90, description="Days to keep location/heartbeat/alarm/attendance/bluetooth records")
|
|
|
|
|
DATA_CLEANUP_INTERVAL_HOURS: int = Field(default=24, description="Hours between automatic cleanup runs")
|
|
|
|
|
|
|
|
|
|
model_config = {"env_file": ".env", "env_file_encoding": "utf-8", "extra": "ignore"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
settings = Settings()
|