"""交易服务入口:FastAPI 应用创建与生命周期管理 与抓取服务(app/scraping/main.py)分成两个进程运行,理由不是「要不要登录」 这一条,而是运行特性根本不同: - 抓取无状态、可重试、可多开实例;这里的写操作**不可逆**,重复提交就是重复下单。 - 登录态 cookie 全局唯一,订单监控是常驻轮询;多开实例会让同一个账号被多个 进程并发操作,也会让轮询重复触发。 - 抓取被限速最多是慢,账号被风控是封号;两者不该共用出口 IP 与请求节奏。 因此本服务只能单实例运行(或按账号分片),扩容靠抓取服务那一侧。 当前只提供登录态的查询与重载;加购、下单、付款与订单监控在此基础上叠加。 """ from __future__ import annotations import logging from contextlib import asynccontextmanager from fastapi import FastAPI from app.shared.api import register_exception_handlers from app.shared.config import get_settings from app.shared.logging_setup import configure_logging from app.shared.telemetry import instrument_app, setup_telemetry, shutdown_telemetry from app.trading.api.routes.auth import router as auth_router from app.trading.api.routes.health import router as health_router from app.trading.container import TradingContainer from app.trading.services.auth_session import AuthSession logger = logging.getLogger(__name__) def build_container() -> TradingContainer: """构建交易服务容器,组装所有依赖""" settings = get_settings() return TradingContainer(settings=settings, auth_session=AuthSession(settings)) @asynccontextmanager async def lifespan(app: FastAPI): """应用生命周期管理:启动时加载登录态,关闭时释放 HTTP 客户端""" container = build_container() app.state.container = container configure_logging(container.settings) # 与抓取侧同样:必须在创建 httpx 客户端之前 setup。 setup_telemetry(container.settings, service_name="rakuten-trading") logger.info( "交易服务启动:%s:%s", container.settings.trading_host, container.settings.trading_port, ) logger.info("当前环境:%s", container.settings.app_env) await container.auth_session.start() try: yield finally: await container.auth_session.close() shutdown_telemetry() def create_app() -> FastAPI: """创建 FastAPI 应用实例,注册路由和异常处理器""" app = FastAPI(title="Rakuten Trading Service", lifespan=lifespan) app.include_router(health_router) app.include_router(auth_router) register_exception_handlers(app) instrument_app(app) return app app = create_app() if __name__ == "__main__": import uvicorn settings = get_settings() configure_logging(settings) uvicorn.run( "app.trading.main:app", host=settings.trading_host, port=settings.trading_port, log_config=None, timeout_keep_alive=120, # 单进程:登录态与后续的订单监控都不能有第二份 workers=1, )