feat(system):优化后端
1.新增后端测试 2.增加了后端的加密 3.增加了i18n(国际化)
This commit is contained in:
+82
-49
@@ -16,6 +16,7 @@ import os
|
||||
from typing import Dict
|
||||
|
||||
from fastapi import FastAPI, WebSocket, Request
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import FileResponse, JSONResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from ray import serve
|
||||
@@ -23,26 +24,68 @@ from ray import serve
|
||||
from .agent import agent_router
|
||||
from .auth import auth_router
|
||||
from .cluster import cluster_router
|
||||
from .health import health_router
|
||||
from .platform.frontend import client_router
|
||||
from .platform.onebot import onebot_router
|
||||
from .provider import provider_router
|
||||
from .resource import resource_router
|
||||
from .workflow import workflow_router
|
||||
from .chat import chat_router
|
||||
from kilostar.utils.error import (
|
||||
DemandError,
|
||||
ModelNotExistError,
|
||||
UserError,
|
||||
UserNotExistError,
|
||||
UserPasswordError,
|
||||
ProviderError,
|
||||
ProviderNotExistError,
|
||||
WorkflowError,
|
||||
WorkflowExit,
|
||||
KiloStarError,
|
||||
BusinessError,
|
||||
InfraError,
|
||||
)
|
||||
from kilostar.utils.logger import get_logger
|
||||
from kilostar.utils.request_context import (
|
||||
bind_request_id,
|
||||
new_request_id,
|
||||
reset_request_id,
|
||||
)
|
||||
from kilostar.utils.i18n import t
|
||||
|
||||
_api_logger = get_logger("api")
|
||||
|
||||
|
||||
def _get_locale(request: Request) -> str | None:
|
||||
"""从请求头解析首选语言,供异常 handler 使用。"""
|
||||
return request.headers.get("accept-language") or None
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
_cors_origins_env = os.environ.get("KILOSTAR_CORS_ORIGINS", "*")
|
||||
_cors_origins = [o.strip() for o in _cors_origins_env.split(",") if o.strip()]
|
||||
_allow_credentials = "*" not in _cors_origins
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=_cors_origins,
|
||||
allow_credentials=_allow_credentials,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
|
||||
@app.middleware("http")
|
||||
async def request_id_middleware(request: Request, call_next):
|
||||
"""请求级 ``request_id`` 注入。
|
||||
|
||||
入口策略:``X-Request-Id`` 头存在则继承(便于网关/前端串联调用链),
|
||||
否则生成新的 UUID。退出时把它写到响应头,方便客户端日志对账。
|
||||
contextvars 让同一请求生命周期内所有协程的日志都自动带上这个 ID。
|
||||
"""
|
||||
incoming = request.headers.get("X-Request-Id", "").strip()
|
||||
request_id = incoming or new_request_id()
|
||||
token = bind_request_id(request_id)
|
||||
try:
|
||||
response = await call_next(request)
|
||||
finally:
|
||||
reset_request_id(token)
|
||||
response.headers["X-Request-Id"] = request_id
|
||||
return response
|
||||
|
||||
app.include_router(health_router) # 健康检查
|
||||
app.include_router(client_router) # 客户端路径
|
||||
app.include_router(onebot_router) # OneBot v11 路径
|
||||
app.include_router(auth_router) # 用户路径
|
||||
app.include_router(provider_router) # 供应商路径
|
||||
app.include_router(resource_router) # 资源路径
|
||||
@@ -52,49 +95,39 @@ app.include_router(workflow_router) # workflow路径
|
||||
app.include_router(chat_router) # chat路径
|
||||
|
||||
|
||||
@app.exception_handler(UserNotExistError)
|
||||
async def user_not_exist_handler(request: Request, exc: UserNotExistError):
|
||||
return JSONResponse(status_code=404, content={"message": "用户不存在"})
|
||||
@app.exception_handler(BusinessError)
|
||||
async def business_error_handler(request: Request, exc: BusinessError):
|
||||
"""业务可预期错误:按 ``http_status`` 返回 4xx,附 ``code`` + 异常消息。"""
|
||||
return JSONResponse(
|
||||
status_code=exc.http_status,
|
||||
content={"code": exc.code, "message": str(exc) or exc.code},
|
||||
)
|
||||
|
||||
|
||||
@app.exception_handler(UserPasswordError)
|
||||
async def user_password_handler(request: Request, exc: UserPasswordError):
|
||||
return JSONResponse(status_code=401, content={"message": "密码错误"})
|
||||
@app.exception_handler(InfraError)
|
||||
async def infra_error_handler(request: Request, exc: InfraError):
|
||||
"""系统失败错误:落日志后返回脱敏的 5xx。"""
|
||||
_api_logger.exception(
|
||||
f"InfraError on {request.method} {request.url.path}: {exc}"
|
||||
)
|
||||
loc = _get_locale(request)
|
||||
return JSONResponse(
|
||||
status_code=exc.http_status,
|
||||
content={"code": exc.code, "message": t("internal_error", accept_language=loc)},
|
||||
)
|
||||
|
||||
|
||||
@app.exception_handler(UserError)
|
||||
async def user_error_handler(request: Request, exc: UserError):
|
||||
return JSONResponse(status_code=400, content={"message": "用户相关错误"})
|
||||
|
||||
|
||||
@app.exception_handler(ProviderNotExistError)
|
||||
async def provider_not_exist_handler(request: Request, exc: ProviderNotExistError):
|
||||
return JSONResponse(status_code=404, content={"message": "服务提供商不存在"})
|
||||
|
||||
|
||||
@app.exception_handler(ProviderError)
|
||||
async def provider_error_handler(request: Request, exc: ProviderError):
|
||||
return JSONResponse(status_code=400, content={"message": "服务提供商错误"})
|
||||
|
||||
|
||||
@app.exception_handler(ModelNotExistError)
|
||||
async def model_not_exist_handler(request: Request, exc: ModelNotExistError):
|
||||
return JSONResponse(status_code=404, content={"message": "模型不存在"})
|
||||
|
||||
|
||||
@app.exception_handler(DemandError)
|
||||
async def demand_error_handler(request: Request, exc: DemandError):
|
||||
return JSONResponse(status_code=400, content={"message": "需求格式错误或不满足"})
|
||||
|
||||
|
||||
@app.exception_handler(WorkflowExit)
|
||||
async def workflow_exit_handler(request: Request, exc: WorkflowExit):
|
||||
return JSONResponse(status_code=400, content={"message": "工作流已退出"})
|
||||
|
||||
|
||||
@app.exception_handler(WorkflowError)
|
||||
async def workflow_error_handler(request: Request, exc: WorkflowError):
|
||||
return JSONResponse(status_code=500, content={"message": "工作流执行错误"})
|
||||
@app.exception_handler(Exception)
|
||||
async def unhandled_exception_handler(request: Request, exc: Exception):
|
||||
"""全局兜底:未预期的异常落日志后返回脱敏的 500,避免泄露 traceback。"""
|
||||
_api_logger.exception(
|
||||
f"Unhandled exception on {request.method} {request.url.path}: {exc}"
|
||||
)
|
||||
loc = _get_locale(request)
|
||||
return JSONResponse(
|
||||
status_code=500,
|
||||
content={"code": "internal_error", "message": t("internal_error", accept_language=loc)},
|
||||
)
|
||||
|
||||
|
||||
base_dir = os.path.dirname(
|
||||
@@ -129,7 +162,7 @@ if os.path.exists(frontend_dir):
|
||||
if os.path.exists(index_path):
|
||||
return FileResponse(index_path)
|
||||
return JSONResponse(
|
||||
status_code=404, content={"detail": "Frontend build not found"}
|
||||
status_code=404, content={"detail": t("frontend_not_found")}
|
||||
)
|
||||
else:
|
||||
import logging
|
||||
|
||||
Reference in New Issue
Block a user