Files
Karlcarlo 53128a6f02 feat(backend): 添加服务管理脚本并配置应用基础参数
- 更新 .gitignore 忽略后端日志目录和 PID 文件
- 新增 app_name 配置项并替换主程序中硬编码的应用名称
- 为配置模型添加 extra: ignore 以兼容额外环境变量
- 新增完整的后端服务管理脚本 serv.sh,支持常用管理操作
2026-05-22 00:51:48 +08:00

123 lines
3.6 KiB
Python

import logging
import os
import time
from contextlib import asynccontextmanager
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse, JSONResponse
from app.config import settings
from app.database import init_db
from app.routers import (
auth,
user,
report,
analysis,
chat,
health_records,
calendar,
doctor,
share,
)
# 日志配置
logging.basicConfig(
level=logging.DEBUG if settings.debug else logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
logger = logging.getLogger(settings.app_name)
@asynccontextmanager
async def lifespan(app: FastAPI):
logger.info("服务启动中...")
await init_db()
os.makedirs(settings.upload_dir, exist_ok=True)
logger.info("服务启动完成 http://%s:%s", settings.host, settings.port)
yield
logger.info("服务关闭")
app = FastAPI(
title=f"{settings.app_name} API",
description="孕期健康检查报告解读后端服务",
version="1.0.0",
lifespan=lifespan,
)
api_prefix = settings.api_prefix.rstrip("/") or ""
if api_prefix and not api_prefix.startswith("/"):
api_prefix = f"/{api_prefix}"
uploads_prefix = settings.uploads_prefix.rstrip("/") or ""
if uploads_prefix and not uploads_prefix.startswith("/"):
uploads_prefix = f"/{uploads_prefix}"
# CORS 配置
cors_origins = (
["*"]
if settings.cors_origins == "*"
else [o.strip() for o in settings.cors_origins.split(",") if o.strip()]
)
app.add_middleware(
CORSMiddleware,
allow_origins=cors_origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# 请求日志中间件
@app.middleware("http")
async def log_requests(request: Request, call_next):
start = time.time()
response = await call_next(request)
duration = round((time.time() - start) * 1000)
logger.info(
"%s %s → %s (%dms)",
request.method,
request.url.path,
response.status_code,
duration,
)
return response
# 全局异常处理
@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
logger.error("未处理异常 %s %s: %s", request.method, request.url.path, exc, exc_info=True)
return JSONResponse(
status_code=500,
content={"detail": "服务器内部错误,请稍后重试"},
)
app.mount(uploads_prefix or "/uploads", StaticFiles(directory=settings.upload_dir), name="uploads")
app.include_router(auth.router, prefix=f"{api_prefix}/auth", tags=["认证"])
app.include_router(user.router, prefix=f"{api_prefix}/user", tags=["用户"])
app.include_router(report.router, prefix=f"{api_prefix}/report", tags=["报告"])
app.include_router(analysis.router, prefix=f"{api_prefix}/analysis", tags=["AI解读"])
app.include_router(chat.router, prefix=f"{api_prefix}/chat", tags=["AI对话"])
app.include_router(health_records.router, prefix=f"{api_prefix}/health-records", tags=["健康档案"])
app.include_router(calendar.router, prefix=f"{api_prefix}/calendar", tags=["产检日历"])
app.include_router(doctor.router, prefix=f"{api_prefix}/doctor", tags=["专家咨询"])
app.include_router(share.router, prefix=f"{api_prefix}/share", tags=["分享"])
@app.get(f"{api_prefix}/health")
async def health_check():
return {"status": "ok", "service": f"{settings.app_name} API"}
@app.get("/")
async def web_demo():
"""Web 体验页面"""
return FileResponse(
os.path.join(os.path.dirname(__file__), "static", "index.html")
)