2026-03-09 14:05:00 +08:00
|
|
|
|
"""
|
|
|
|
|
|
Weibo-HotSign API Service
|
|
|
|
|
|
Main FastAPI application entry point — account management, task config, signin logs.
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
2026-03-18 09:19:29 +08:00
|
|
|
|
import logging
|
|
|
|
|
|
|
2026-03-09 14:05:00 +08:00
|
|
|
|
from fastapi import FastAPI, Request
|
|
|
|
|
|
from fastapi.exceptions import RequestValidationError
|
|
|
|
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
|
|
|
|
from starlette.exceptions import HTTPException as StarletteHTTPException
|
|
|
|
|
|
|
|
|
|
|
|
from shared.response import success_response, error_response
|
|
|
|
|
|
from api_service.app.routers import accounts, tasks, signin_logs
|
|
|
|
|
|
|
2026-03-18 09:19:29 +08:00
|
|
|
|
|
|
|
|
|
|
# 过滤 /health 的 access log,避免日志刷屏
|
|
|
|
|
|
class _HealthFilter(logging.Filter):
|
|
|
|
|
|
def filter(self, record: logging.LogRecord) -> bool:
|
|
|
|
|
|
msg = record.getMessage()
|
|
|
|
|
|
return "/health" not in msg
|
|
|
|
|
|
|
|
|
|
|
|
logging.getLogger("uvicorn.access").addFilter(_HealthFilter())
|
|
|
|
|
|
|
2026-03-09 14:05:00 +08:00
|
|
|
|
app = FastAPI(
|
|
|
|
|
|
title="Weibo-HotSign API Service",
|
|
|
|
|
|
version="1.0.0",
|
|
|
|
|
|
docs_url="/docs",
|
|
|
|
|
|
redoc_url="/redoc",
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# CORS
|
|
|
|
|
|
app.add_middleware(
|
|
|
|
|
|
CORSMiddleware,
|
|
|
|
|
|
allow_origins=["http://localhost:3000", "http://localhost:80"],
|
|
|
|
|
|
allow_credentials=True,
|
|
|
|
|
|
allow_methods=["*"],
|
|
|
|
|
|
allow_headers=["*"],
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---- Global exception handlers (unified response format) ----
|
|
|
|
|
|
|
|
|
|
|
|
@app.exception_handler(StarletteHTTPException)
|
|
|
|
|
|
async def http_exception_handler(request: Request, exc: StarletteHTTPException):
|
|
|
|
|
|
return error_response(
|
|
|
|
|
|
exc.detail,
|
|
|
|
|
|
f"HTTP_{exc.status_code}",
|
|
|
|
|
|
status_code=exc.status_code,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.exception_handler(RequestValidationError)
|
|
|
|
|
|
async def validation_exception_handler(request: Request, exc: RequestValidationError):
|
|
|
|
|
|
details = [
|
|
|
|
|
|
{"field": e["loc"][-1] if e["loc"] else "unknown", "message": e["msg"]}
|
|
|
|
|
|
for e in exc.errors()
|
|
|
|
|
|
]
|
|
|
|
|
|
return error_response(
|
|
|
|
|
|
"Validation failed",
|
|
|
|
|
|
"VALIDATION_ERROR",
|
|
|
|
|
|
details=details,
|
|
|
|
|
|
status_code=400,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---- Routers ----
|
|
|
|
|
|
|
|
|
|
|
|
app.include_router(accounts.router)
|
|
|
|
|
|
app.include_router(tasks.router)
|
|
|
|
|
|
app.include_router(signin_logs.router)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---- Health / root ----
|
|
|
|
|
|
|
|
|
|
|
|
@app.get("/")
|
|
|
|
|
|
async def root():
|
|
|
|
|
|
return success_response(
|
|
|
|
|
|
{"service": "Weibo-HotSign API Service", "version": "1.0.0"},
|
|
|
|
|
|
"Service is running",
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.get("/health")
|
|
|
|
|
|
async def health_check():
|
|
|
|
|
|
return success_response({"status": "healthy"})
|