Files
grok2api/main.py
T

240 lines
7.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Grok2API 应用入口
FastAPI 应用初始化和路由注册
"""
from contextlib import asynccontextmanager
import argparse
import os
import platform
import sys
from pathlib import Path
from dotenv import load_dotenv
BASE_DIR = Path(__file__).resolve().parent
APP_DIR = BASE_DIR / "app"
# Ensure the project root is on sys.path (helps when Vercel sets a different CWD)
if str(BASE_DIR) not in sys.path:
sys.path.insert(0, str(BASE_DIR))
env_file = BASE_DIR / ".env"
if env_file.exists():
load_dotenv(env_file)
from fastapi import FastAPI # noqa: E402
from fastapi.middleware.cors import CORSMiddleware # noqa: E402
from fastapi import Depends # noqa: E402
from app.core.auth import verify_api_key # noqa: E402
from app.core.config import get_config # noqa: E402
from app.core.logger import logger, setup_logging # noqa: E402
from app.core.exceptions import register_exception_handlers # noqa: E402
from app.core.response_middleware import ResponseLoggerMiddleware # noqa: E402
from app.api.v1.chat import router as chat_router # noqa: E402
from app.api.v1.image import router as image_router # noqa: E402
from app.api.v1.nsfw import router as nsfw_router # noqa: E402
from app.api.v1.files import router as files_router # noqa: E402
from app.api.v1.models import router as models_router # noqa: E402
from app.api.v1.response import router as responses_router # noqa: E402
from app.services.token import get_scheduler # noqa: E402
from app.api.v1.admin_api import router as admin_router
from app.api.v1.public_api import router as public_router
from app.api.v1.video_api import router as video_router
from app.api.pages import router as pages_router
from fastapi.staticfiles import StaticFiles
# 初始化日志
setup_logging(
level=os.getenv("LOG_LEVEL", "INFO"), json_console=False, file_logging=True
)
@asynccontextmanager
async def lifespan(app: FastAPI):
"""应用生命周期管理"""
# 1. 注册服务默认配置
from app.core.config import config, register_defaults
from app.services.grok.defaults import get_grok_defaults
register_defaults(get_grok_defaults())
# 2. 加载配置
await config.load()
# 3. 启动服务显示
logger.info("Starting Grok2API...")
logger.info(f"Platform: {platform.system()} {platform.release()}")
logger.info(f"Python: {sys.version.split()[0]}")
# 4. 启动 Token 刷新调度器
refresh_enabled = get_config("token.auto_refresh", True)
if refresh_enabled:
basic_interval = get_config("token.refresh_interval_hours", 8)
super_interval = get_config("token.super_refresh_interval_hours", 2)
interval = min(basic_interval, super_interval)
scheduler = get_scheduler(interval)
scheduler.start()
# 5. 启动 cf_clearance 自动刷新
# 环境变量 FLARESOLVERR_URL 会作为初始值写入配置(兼容旧部署方式)
_flaresolverr_env = os.getenv("FLARESOLVERR_URL", "")
if _flaresolverr_env and not get_config("proxy.flaresolverr_url"):
await config.update({
"proxy": {
"enabled": True,
"flaresolverr_url": _flaresolverr_env,
"refresh_interval": int(os.getenv("CF_REFRESH_INTERVAL", "600")),
"timeout": int(os.getenv("CF_TIMEOUT", "60")),
}
})
from app.services.cf_refresh import start as cf_refresh_start
cf_refresh_start()
logger.info("Application startup complete.")
yield
# 关闭
logger.info("Shutting down Grok2API...")
from app.services.cf_refresh import stop as cf_refresh_stop
cf_refresh_stop()
from app.core.storage import StorageFactory
if StorageFactory._instance:
await StorageFactory._instance.close()
if refresh_enabled:
scheduler = get_scheduler()
scheduler.stop()
def create_app() -> FastAPI:
"""创建 FastAPI 应用"""
app = FastAPI(
title="Grok2API",
lifespan=lifespan,
)
# CORS 配置
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# 请求日志和 ID 中间件
app.add_middleware(ResponseLoggerMiddleware)
# 注册异常处理器
register_exception_handlers(app)
# 注册路由
app.include_router(
chat_router, prefix="/v1", dependencies=[Depends(verify_api_key)]
)
app.include_router(
image_router, prefix="/v1", dependencies=[Depends(verify_api_key)]
)
app.include_router(
nsfw_router, prefix="/v1", dependencies=[Depends(verify_api_key)]
)
app.include_router(
models_router, prefix="/v1", dependencies=[Depends(verify_api_key)]
)
app.include_router(
responses_router, prefix="/v1", dependencies=[Depends(verify_api_key)]
)
# 兼容部分客户端直接请求 /responses(不带 /v1)
app.include_router(
responses_router, dependencies=[Depends(verify_api_key)]
)
app.include_router(
video_router, prefix="/v1", dependencies=[Depends(verify_api_key)]
)
app.include_router(files_router, prefix="/v1/files")
# 静态文件服务
static_dir = APP_DIR / "static"
if static_dir.exists():
app.mount("/static", StaticFiles(directory=static_dir), name="static")
# 注册管理与公共路由
app.include_router(admin_router, prefix="/v1/admin")
app.include_router(public_router, prefix="/v1/public")
app.include_router(pages_router)
return app
app = create_app()
if __name__ == "__main__":
import uvicorn
parser = argparse.ArgumentParser(description="启动 Grok2API 服务")
parser.add_argument(
"--host",
default=os.getenv("SERVER_HOST", "0.0.0.0"),
help="监听地址,默认读取 SERVER_HOST 或 0.0.0.0",
)
parser.add_argument(
"--port",
type=int,
default=int(os.getenv("SERVER_PORT", "8000")),
help="监听端口,默认读取 SERVER_PORT 或 8000",
)
parser.add_argument(
"--workers",
type=int,
default=int(os.getenv("SERVER_WORKERS", "1")),
help="Worker 数量,默认读取 SERVER_WORKERS 或 1",
)
parser.add_argument(
"--reload",
action="store_true",
help="开启热重载(开发模式)",
)
args = parser.parse_args()
host = args.host
port = args.port
workers = args.workers
reload_enabled = args.reload
# 平台检查
is_windows = platform.system() == "Windows"
# 自动降级
if is_windows and workers > 1:
logger.warning(
f"Windows platform detected. Multiple workers ({workers}) is not supported. "
"Using single worker instead."
)
workers = 1
# uvicorn 不支持 reload 与多 worker 同时启用
if reload_enabled and workers > 1:
logger.warning(
f"Reload mode detected. Multiple workers ({workers}) is not supported. "
"Using single worker instead."
)
workers = 1
uvicorn.run(
"main:app",
host=host,
port=port,
workers=workers,
reload=reload_enabled,
log_level=os.getenv("LOG_LEVEL", "INFO").lower(),
)