|
20 | 20 | import json |
21 | 21 | import logging |
22 | 22 | import re |
| 23 | +import threading |
23 | 24 | from datetime import datetime |
24 | 25 | from typing import Optional, Union, Dict, Any |
25 | 26 |
|
26 | | -from fastapi import APIRouter, HTTPException, Depends, Query |
| 27 | +from fastapi import APIRouter, HTTPException, Depends, Query, BackgroundTasks |
27 | 28 | from fastapi.responses import JSONResponse, StreamingResponse |
28 | 29 |
|
29 | 30 | from api.deps import get_config_dep |
|
38 | 39 | TaskInfo, |
39 | 40 | TaskListResponse, |
40 | 41 | DuplicateTaskErrorResponse, |
| 42 | + MarketReviewRequest, |
| 43 | + MarketReviewAccepted, |
41 | 44 | ) |
42 | 45 | from api.v1.schemas.common import ErrorResponse |
43 | 46 | from api.v1.schemas.history import ( |
|
69 | 72 | router = APIRouter() |
70 | 73 |
|
71 | 74 | _SUPPORTED_FREE_TEXT_RE = re.compile(r"^[A-Za-z0-9.*\-+\u3400-\u9fff\s]+$") |
| 75 | +_market_review_lock = threading.Lock() |
| 76 | +_market_review_running = False |
| 77 | + |
| 78 | + |
| 79 | +def _run_market_review_background(send_notification: bool) -> None: |
| 80 | + """Run market review after the API response has been accepted.""" |
| 81 | + global _market_review_running |
| 82 | + try: |
| 83 | + from src.core.market_review import run_market_review |
| 84 | + from src.notification import NotificationService |
| 85 | + |
| 86 | + notifier = NotificationService() |
| 87 | + run_market_review(notifier, send_notification=send_notification) |
| 88 | + except Exception as exc: |
| 89 | + logger.error("大盘复盘后台任务失败: %s", exc, exc_info=True) |
| 90 | + finally: |
| 91 | + with _market_review_lock: |
| 92 | + _market_review_running = False |
72 | 93 |
|
73 | 94 |
|
74 | 95 | def _invalid_analysis_input_error() -> HTTPException: |
@@ -392,6 +413,47 @@ def _handle_sync_analysis( |
392 | 413 | ) |
393 | 414 |
|
394 | 415 |
|
| 416 | +# ============================================================ |
| 417 | +# POST /market-review - 触发大盘复盘 |
| 418 | +# ============================================================ |
| 419 | + |
| 420 | +@router.post( |
| 421 | + "/market-review", |
| 422 | + response_model=MarketReviewAccepted, |
| 423 | + status_code=202, |
| 424 | + responses={ |
| 425 | + 202: {"description": "大盘复盘任务已接受", "model": MarketReviewAccepted}, |
| 426 | + 409: {"description": "大盘复盘正在执行", "model": ErrorResponse}, |
| 427 | + 500: {"description": "提交失败", "model": ErrorResponse}, |
| 428 | + }, |
| 429 | + summary="触发大盘复盘", |
| 430 | + description="提交一个后台大盘复盘任务,复用 CLI 的大盘复盘链路并保存报告。", |
| 431 | +) |
| 432 | +def trigger_market_review( |
| 433 | + request: MarketReviewRequest, |
| 434 | + background_tasks: BackgroundTasks, |
| 435 | +) -> MarketReviewAccepted: |
| 436 | + """Trigger market review from Web/API without blocking the request.""" |
| 437 | + global _market_review_running |
| 438 | + with _market_review_lock: |
| 439 | + if _market_review_running: |
| 440 | + raise HTTPException( |
| 441 | + status_code=409, |
| 442 | + detail={ |
| 443 | + "error": "duplicate_market_review", |
| 444 | + "message": "大盘复盘正在执行中,请稍后再试", |
| 445 | + }, |
| 446 | + ) |
| 447 | + _market_review_running = True |
| 448 | + |
| 449 | + background_tasks.add_task(_run_market_review_background, request.send_notification) |
| 450 | + return MarketReviewAccepted( |
| 451 | + status="accepted", |
| 452 | + message="大盘复盘任务已提交,完成后会保存报告并按配置推送通知", |
| 453 | + send_notification=request.send_notification, |
| 454 | + ) |
| 455 | + |
| 456 | + |
395 | 457 | # ============================================================ |
396 | 458 | # GET /tasks - 获取任务列表 |
397 | 459 | # ============================================================ |
|
0 commit comments