feat: 微服务架构拆分和前后端优化
后端: - 拆分出 agent_service, runtime_service, trading_service, news_service - Gateway 模块化拆分 (gateway_*.py) - 添加 domains/ 领域层 - 新增 control_client, runtime_client - 更新 start-dev.sh 支持 split 服务模式 前端: - 完善 API 服务层 (newsApi, tradingApi) - 更新 vite.config.js - Explain 组件优化 测试: - 添加多个服务 app 测试 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
153
backend/apps/news_service.py
Normal file
153
backend/apps/news_service.py
Normal file
@@ -0,0 +1,153 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""News and explain FastAPI surface."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from fastapi import Depends, FastAPI, Query
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
|
||||
from backend.data.market_store import MarketStore
|
||||
from backend.domains import news as news_domain
|
||||
|
||||
|
||||
def get_market_store() -> MarketStore:
|
||||
"""Create a market store dependency."""
|
||||
return MarketStore()
|
||||
|
||||
|
||||
def create_app() -> FastAPI:
|
||||
"""Create the news/explain service app."""
|
||||
app = FastAPI(
|
||||
title="EvoTraders News Service",
|
||||
description="Read-only news enrichment and explain service surface extracted from the monolith",
|
||||
version="0.1.0",
|
||||
)
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
@app.get("/health")
|
||||
async def health_check() -> dict[str, str]:
|
||||
return {"status": "healthy", "service": "news-service"}
|
||||
|
||||
@app.get("/api/enriched-news")
|
||||
async def api_get_enriched_news(
|
||||
ticker: str = Query(..., min_length=1),
|
||||
start_date: str | None = Query(None),
|
||||
end_date: str | None = Query(None),
|
||||
limit: int = Query(100, ge=1, le=1000),
|
||||
store: MarketStore = Depends(get_market_store),
|
||||
) -> dict[str, Any]:
|
||||
return news_domain.get_enriched_news(
|
||||
store,
|
||||
ticker=ticker,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
limit=limit,
|
||||
)
|
||||
|
||||
@app.get("/api/news-for-date")
|
||||
async def api_get_news_for_date(
|
||||
ticker: str = Query(..., min_length=1),
|
||||
date: str = Query(...),
|
||||
limit: int = Query(20, ge=1, le=100),
|
||||
store: MarketStore = Depends(get_market_store),
|
||||
) -> dict[str, Any]:
|
||||
return news_domain.get_news_for_date(
|
||||
store,
|
||||
ticker=ticker,
|
||||
date=date,
|
||||
limit=limit,
|
||||
)
|
||||
|
||||
@app.get("/api/news-timeline")
|
||||
async def api_get_news_timeline(
|
||||
ticker: str = Query(..., min_length=1),
|
||||
start_date: str = Query(...),
|
||||
end_date: str = Query(...),
|
||||
store: MarketStore = Depends(get_market_store),
|
||||
) -> dict[str, Any]:
|
||||
return news_domain.get_news_timeline(
|
||||
store,
|
||||
ticker=ticker,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
)
|
||||
|
||||
@app.get("/api/categories")
|
||||
async def api_get_categories(
|
||||
ticker: str = Query(..., min_length=1),
|
||||
start_date: str | None = Query(None),
|
||||
end_date: str | None = Query(None),
|
||||
limit: int = Query(200, ge=1, le=1000),
|
||||
store: MarketStore = Depends(get_market_store),
|
||||
) -> dict[str, Any]:
|
||||
return news_domain.get_news_categories(
|
||||
store,
|
||||
ticker=ticker,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
limit=limit,
|
||||
)
|
||||
|
||||
@app.get("/api/similar-days")
|
||||
async def api_get_similar_days(
|
||||
ticker: str = Query(..., min_length=1),
|
||||
date: str = Query(...),
|
||||
n_similar: int = Query(5, ge=1, le=20),
|
||||
store: MarketStore = Depends(get_market_store),
|
||||
) -> dict[str, Any]:
|
||||
return news_domain.get_similar_days_payload(
|
||||
store,
|
||||
ticker=ticker,
|
||||
date=date,
|
||||
n_similar=n_similar,
|
||||
)
|
||||
|
||||
@app.get("/api/stories/{ticker}")
|
||||
async def api_get_story(
|
||||
ticker: str,
|
||||
as_of_date: str = Query(...),
|
||||
store: MarketStore = Depends(get_market_store),
|
||||
) -> dict[str, Any]:
|
||||
return news_domain.get_story_payload(
|
||||
store,
|
||||
ticker=ticker,
|
||||
as_of_date=as_of_date,
|
||||
)
|
||||
|
||||
@app.get("/api/range-explain")
|
||||
async def api_get_range_explain(
|
||||
ticker: str = Query(..., min_length=1),
|
||||
start_date: str = Query(...),
|
||||
end_date: str = Query(...),
|
||||
article_ids: list[str] = Query(default=[]),
|
||||
limit: int = Query(100, ge=1, le=500),
|
||||
store: MarketStore = Depends(get_market_store),
|
||||
) -> dict[str, Any]:
|
||||
return news_domain.get_range_explain_payload(
|
||||
store,
|
||||
ticker=ticker,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
article_ids=article_ids,
|
||||
limit=limit,
|
||||
)
|
||||
|
||||
return app
|
||||
|
||||
|
||||
app = create_app()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
|
||||
uvicorn.run(app, host="0.0.0.0", port=8002)
|
||||
Reference in New Issue
Block a user