feat: backend 架构基座(copier 预生成,与功能无关的横切层)
每个生成的 backend 第一天即有完整分层结构 + 能跑的横切基础设施:
- 空层包:domain / application/services / infrastructure/db(待功能填)
- routers:health(存活/就绪) + ws(WebSocket 代理骨架)
- middleware:request_id(注入+loguru 上下文)+ access_log
- exceptions:AppException 基类 + 统一 handler(标准错误响应)
- infrastructure/db/base:engine/session/Base(SQLite WAL+busy_timeout / PG 连接池)+ get_db
- utils:ids + files(work_dir/会话目录/防穿越)
- core/logging:加文件 sink 到 {WORK_DIR}/logs(运行时建,gitignore)
- main.py:装配中间件/异常/路由
- 运行时目录(logs、{uuid} 临时)由代码创建,不入库
验证:sqlite/postgresql 两配置 ruff+mypy 全过,import 成功,7 条路由就位。
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.responses import JSONResponse
|
||||
from loguru import logger
|
||||
|
||||
from .base import AppException
|
||||
|
||||
|
||||
async def app_exception_handler(request: Request, exc: AppException) -> JSONResponse:
|
||||
request_id = getattr(request.state, "request_id", None)
|
||||
logger.warning("AppException | code={} msg={}", exc.code, exc.message)
|
||||
return JSONResponse(
|
||||
status_code=exc.status_code,
|
||||
content={
|
||||
"error": {
|
||||
"code": exc.code,
|
||||
"message": exc.message,
|
||||
"request_id": request_id,
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
async def global_exception_handler(request: Request, exc: Exception) -> JSONResponse:
|
||||
request_id = getattr(request.state, "request_id", None)
|
||||
logger.exception("Unhandled exception")
|
||||
return JSONResponse(
|
||||
status_code=500,
|
||||
content={
|
||||
"error": {
|
||||
"code": "INTERNAL_ERROR",
|
||||
"message": "服务器内部错误",
|
||||
"request_id": request_id,
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def register_exception_handlers(app: FastAPI) -> None:
|
||||
# handler 形参类型为具体异常,与 Starlette 宽签名不完全一致,忽略该告警
|
||||
app.add_exception_handler(AppException, app_exception_handler) # type: ignore[arg-type]
|
||||
app.add_exception_handler(Exception, global_exception_handler)
|
||||
Reference in New Issue
Block a user