from typing import Union from fastapi import FastAPI, Request, status from fastapi.responses import JSONResponse, Response, StreamingResponse from src.blackbox.blackbox_factory import BlackboxFactory from src.log.loki_config import LokiLogger import logging from fastapi.middleware.cors import CORSMiddleware from injector import Injector import yaml app = FastAPI() app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) injector = Injector() blackbox_factory = injector.get(BlackboxFactory) with open(".env.yaml", "r") as f: config = yaml.safe_load(f) logger = LokiLogger( # url=config["loki"]["url"], # username=config["loki"]["username"], # password=config["loki"]["password"], tags=config["loki"]["labels"], logger_name=__name__, level=logging.DEBUG ).get_logger() @app.exception_handler(Exception) async def catch_all_exceptions(request: Request, exc: Exception): """ 捕获所有在 ASGI 应用中未被处理的异常,并记录到 Loki。 """ logger.error( f"Unhandled exception in ASGI application for path: {request.url.path}, method: {request.method} - {exc}", exc_info=True, # 必须为 True,才能获取完整的堆栈信息 extra={ "path": request.url.path, "method": request.method, "error_type": type(exc).__name__, "error_message": str(exc) } ) return JSONResponse( status_code=500, content={"message": "Internal Server Error", "detail": "An unexpected error occurred."} ) @app.post("/") @app.get("/") async def blackbox(blackbox_name: Union[str, None] = None, request: Request = None): if not blackbox_name: return JSONResponse(content={"error": "blackbox_name is required"}, status_code=status.HTTP_400_BAD_REQUEST) try: box = blackbox_factory.get_blackbox(blackbox_name) except ValueError as e: logger.error(f"获取 blackbox 失败: {blackbox_name} - {e}", exc_info=True) return JSONResponse(content={"error": "value error"}, status_code=status.HTTP_400_BAD_REQUEST) try: response = await box.fast_api_handler(request) # 检查响应的状态码,如果 >= 400,则认为是错误响应并记录 if response.status_code >= 400: try: decoded_content = response.body.decode('utf-8', errors='ignore') logger.error(f"Blackbox 返回错误响应: URL={request.url}, Method={request.method}, Status={response.status_code}, Content={decoded_content}") except Exception as body_exc: logger.error(f"Blackbox {blackbox_name} 返回错误响应: URL={request.url}, Method={request.method}, Status={response.status_code}, 无法解析响应内容: {body_exc}") return response except Exception as e: logger.error(f"Blackbox 内部抛出异常: URL={request.url}, Method={request.method}, 异常报错: {type(e).__name__}: {e}", exc_info=True) return JSONResponse( status_code=500, content={"message": "Internal Server Error", "detail": "An unexpected error occurred."} ) # @app.get("/audio/{filename}") # async def serve_audio(filename: str): # import os # # 确保文件存在 # if os.path.exists(filename): # with open(filename, 'rb') as f: # audio_data = f.read() # if filename.endswith(".mp3"): # # 对于 MP3 文件,设置为 inline 以在浏览器中播放 # return Response(content=audio_data, media_type="audio/mpeg", headers={"Content-Disposition": f"inline; filename={filename}"}) # elif filename.endswith(".wav"): # # 对于 WAV 文件,设置为 inline 以在浏览器中播放 # return Response(content=audio_data, media_type="audio/wav", headers={"Content-Disposition": f"inline; filename={filename}"}) # else: # return JSONResponse(content={"error": f"{filename} not found"}, status_code=status.HTTP_404_NOT_FOUND) @app.get("/audio/audio_files/{filename}") async def serve_audio(filename: str): import os import aiofiles filename = os.path.join("audio_files", filename) # 确保文件存在 if os.path.exists(filename): try: # 使用 aiofiles 异步读取文件 async with aiofiles.open(filename, 'rb') as f: audio_data = await f.read() # 根据文件类型返回正确的音频响应 if filename.endswith(".mp3"): return Response(content=audio_data, media_type="audio/mpeg", headers={"Content-Disposition": f"inline; filename={filename}"}) elif filename.endswith(".wav"): return Response(content=audio_data, media_type="audio/wav", headers={"Content-Disposition": f"inline; filename={filename}"}) else: return JSONResponse(content={"error": "Unsupported audio format"}, status_code=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE) except asyncio.CancelledError: # 处理任务被取消的情况 return JSONResponse(content={"error": "Request was cancelled"}, status_code=status.HTTP_400_BAD_REQUEST) except Exception as e: # 捕获其他异常 return JSONResponse(content={"error": str(e)}, status_code=status.HTTP_500_INTERNAL_SERVER_ERROR) else: return JSONResponse(content={"error": f"{filename} not found"}, status_code=status.HTTP_404_NOT_FOUND) # @app.get("/audio/{filename}") # async def serve_audio(filename: str): # import os # file_path = f"audio/{filename}" # # 检查文件是否存在 # if os.path.exists(file_path): # return StreamingResponse(open(file_path, "rb"), media_type="audio/mpeg" if filename.endswith(".mp3") else "audio/wav") # else: # return JSONResponse(content={"error": f"{filename} not found"}, status_code=404)