Files
daily_stock_analysis/api/app.py
Krane 9847157980 Feature/React web support 新的WebUI (#256)
* feat(web): add FastAPI & React web connect

* feat: add FastAPI package

* feat: system base struct

* feat: frontend base struct

* feat: 基础分析接口,历史记录接口

* fix: 使用正确的 FastAPI 方法定义,避免线程阻塞

* fix: 对接历史报告页、详情页

* fix: 修复接口问题

* fix: 网络配置 允许公网访问 跨域配置

* fix: 页面打包 & 提供 Server 静态访问

* fix: 优化dock栏样式

* fix: 优化图标样式

* fix: 修改部分配色

* fix: 删除垃圾文档

* fix: 删除垃圾代码

* fix: 驼峰转换工具

* fix: 历史记录列表滚动加载(分页)

* feat: 显示分析中任务

* fix: 调整布局

* fix: 优化 Market Sentiment 组件动效

* fix: 历史列表组件bug

* fix: FastAPI 日志配置

* feat: 新闻历史接口

* feat: 资讯列表

* feat: 优化布局

* fix: 修复页面元素变宽问题

* fix: 中文标题

* fix: 任务列表状态显示问题

* fix: 抽取日志配置

* fix: 优化报告价格显示

* fix: 修复编译错误

* fix: FastAPI 启动提取到 main.py

* fix: 补充新web-ui启动文档

* fix: 默认发送通知

* fix: FastAPI 模式适配 docker

* fix: FastAPI 模式文档

* Update api/v1/endpoints/stocks.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix: package-lock.json

* fix: 修改错别字

* fix: 更新文档说明

* fix: 更新文档说明

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-05 20:56:38 +08:00

165 lines
5.1 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.

# -*- coding: utf-8 -*-
"""
===================================
FastAPI 应用工厂模块
===================================
职责:
1. 创建和配置 FastAPI 应用实例
2. 配置 CORS 中间件
3. 注册路由和异常处理器
4. 托管前端静态文件(生产模式)
使用方式:
from api.app import create_app
app = create_app()
"""
import os
from datetime import datetime
from pathlib import Path
from typing import Optional
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse
from api.v1 import api_v1_router
from api.middlewares.error_handler import add_error_handlers
from api.v1.schemas.common import RootResponse, HealthResponse
def create_app(static_dir: Optional[Path] = None) -> FastAPI:
"""
创建并配置 FastAPI 应用实例
Args:
static_dir: 静态文件目录路径(可选,默认为项目根目录下的 static
Returns:
配置完成的 FastAPI 应用实例
"""
# 默认静态文件目录
if static_dir is None:
static_dir = Path(__file__).parent.parent / "static"
# 创建 FastAPI 实例
app = FastAPI(
title="Daily Stock Analysis API",
description=(
"A股/港股/美股自选股智能分析系统 API\n\n"
"## 功能模块\n"
"- 股票分析:触发 AI 智能分析\n"
"- 历史记录:查询历史分析报告\n"
"- 股票数据:获取行情数据\n\n"
"## 认证方式\n"
"当前版本暂无认证要求"
),
version="1.0.0",
)
# ============================================================
# CORS 配置
# ============================================================
allowed_origins = [
"http://localhost:5173",
"http://127.0.0.1:5173",
"http://localhost:3000",
"http://127.0.0.1:3000",
]
# 从环境变量添加额外的允许来源
extra_origins = os.environ.get("CORS_ORIGINS", "")
if extra_origins:
allowed_origins.extend([o.strip() for o in extra_origins.split(",") if o.strip()])
# 允许所有来源(开发/演示用)
if os.environ.get("CORS_ALLOW_ALL", "").lower() == "true":
allowed_origins = ["*"]
app.add_middleware(
CORSMiddleware,
allow_origins=allowed_origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# ============================================================
# 注册路由
# ============================================================
app.include_router(api_v1_router)
add_error_handlers(app)
# ============================================================
# 根路由和健康检查
# ============================================================
has_frontend = static_dir.exists() and (static_dir / "index.html").exists()
if has_frontend:
@app.get("/", include_in_schema=False)
async def root():
"""根路由 - 返回前端页面"""
return FileResponse(static_dir / "index.html")
else:
@app.get(
"/",
response_model=RootResponse,
tags=["Health"],
summary="API 根路由",
description="返回 API 运行状态信息"
)
async def root() -> RootResponse:
"""根路由 - API 状态信息"""
return RootResponse(
message="Daily Stock Analysis API is running",
version="1.0.0"
)
@app.get(
"/api/health",
response_model=HealthResponse,
tags=["Health"],
summary="健康检查",
description="用于负载均衡器或监控系统检查服务状态"
)
async def health_check() -> HealthResponse:
"""健康检查接口"""
return HealthResponse(
status="ok",
timestamp=datetime.now().isoformat()
)
# ============================================================
# 静态文件托管(前端 SPA
# ============================================================
if has_frontend:
# 挂载静态资源目录
assets_dir = static_dir / "assets"
if assets_dir.exists():
app.mount("/assets", StaticFiles(directory=assets_dir), name="assets")
# SPA 路由回退
@app.get("/{full_path:path}", include_in_schema=False)
async def serve_spa(request: Request, full_path: str):
"""SPA 路由回退 - 非 API 路由返回 index.html"""
if full_path.startswith("api/"):
return None
file_path = static_dir / full_path
if file_path.exists() and file_path.is_file():
return FileResponse(file_path)
return FileResponse(static_dir / "index.html")
return app
# 默认应用实例(供 uvicorn 直接使用)
app = create_app()