refactor(api): TOML 配置 SSOT、统一错误契约、Auth/事务加固与可观测性 (#33)
配置 SSOT(TOML + .env) 统一错误契约 Auth 与事务边界 Redis / Celery 可靠性:业务 Redis(DB/0)与 Celery broker/backend(DB/1)显式拆分;连接池、sync client 可观测性(OpenTelemetry + LGTM)
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
from app.core.config import settings
|
||||
from app.features.plan.schemas import PlanResponse
|
||||
|
||||
ENABLE_TEST_PLAN = (settings.enable_test_plan or "").lower() in ("1", "true", "yes")
|
||||
ENABLE_TEST_PLAN = settings.enable_test_plan
|
||||
|
||||
AVAILABLE_PLANS = [
|
||||
PlanResponse(
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
"""Plan feature dependencies: get_plan_service."""
|
||||
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import Depends
|
||||
|
||||
from app.core.dependencies import get_current_user
|
||||
from app.features.plan.service import PlanService
|
||||
from app.features.quota.deps import get_quota_service
|
||||
from app.features.quota.service import QuotaService
|
||||
from app.features.user.models import User
|
||||
from app.core.deps_types import DbDep
|
||||
|
||||
|
||||
def get_plan_service(
|
||||
quota_service: QuotaService = Depends(get_quota_service),
|
||||
quota_service: Annotated[QuotaService, Depends(get_quota_service)],
|
||||
) -> PlanService:
|
||||
return PlanService(quota_service=quota_service)
|
||||
|
||||
|
||||
PlanServiceDep = Annotated[PlanService, Depends(get_plan_service)]
|
||||
CurrentUserDep = Annotated[User, Depends(get_current_user)]
|
||||
|
||||
@@ -2,36 +2,29 @@
|
||||
订阅计划路由。
|
||||
"""
|
||||
|
||||
from typing import List
|
||||
from fastapi import APIRouter
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
|
||||
from app.core.dependencies import get_current_user
|
||||
from app.features.plan.deps import get_plan_service
|
||||
from app.core.openapi import error_responses
|
||||
from app.features.plan.deps import CurrentUserDep, PlanServiceDep
|
||||
from app.features.plan.schemas import CurrentPlanResponse, PlanResponse
|
||||
from app.features.plan.service import PlanService, get_plans_for_api
|
||||
from app.features.user.models import User
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/api/plans",
|
||||
tags=["plans"],
|
||||
responses={
|
||||
401: {"description": "认证失败"},
|
||||
404: {"description": "资源不存在"},
|
||||
},
|
||||
responses=error_responses(401, 404),
|
||||
)
|
||||
|
||||
|
||||
@router.get("", response_model=List[PlanResponse])
|
||||
async def get_plans():
|
||||
@router.get("", response_model=list[PlanResponse])
|
||||
def get_plans(service: PlanServiceDep) -> list[PlanResponse]:
|
||||
"""获取所有可用的订阅计划(开发环境 ENABLE_TEST_PLAN=1 时包含「一分钱测试版」)。"""
|
||||
return get_plans_for_api()
|
||||
return service.get_plans_for_api()
|
||||
|
||||
|
||||
@router.get("/current", response_model=CurrentPlanResponse)
|
||||
async def get_current_plan(
|
||||
current_user: User = Depends(get_current_user),
|
||||
service: PlanService = Depends(get_plan_service),
|
||||
):
|
||||
current_user: CurrentUserDep,
|
||||
service: PlanServiceDep,
|
||||
) -> CurrentPlanResponse:
|
||||
"""获取当前用户的订阅计划信息"""
|
||||
return await service.get_current_plan_response(current_user)
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
@@ -9,17 +7,24 @@ class PlanResponse(BaseModel):
|
||||
display_name: str
|
||||
price: float
|
||||
currency: str
|
||||
features: List[str]
|
||||
max_conversations: Optional[int] = None
|
||||
max_chapters: Optional[int] = None
|
||||
max_words: Optional[int] = None
|
||||
features: list[str]
|
||||
max_conversations: int | None = None
|
||||
max_chapters: int | None = None
|
||||
max_words: int | None = None
|
||||
is_popular: bool = False
|
||||
|
||||
|
||||
class PlanUsageResponse(BaseModel):
|
||||
conversations: int
|
||||
chapters: int
|
||||
max_conversations: int | None = None
|
||||
max_chapters: int | None = None
|
||||
|
||||
|
||||
class CurrentPlanResponse(BaseModel):
|
||||
plan_id: str
|
||||
plan_name: str
|
||||
subscription_type: str
|
||||
expires_at: Optional[str] = None
|
||||
features: List[str]
|
||||
usage: dict
|
||||
expires_at: str | None = None
|
||||
features: list[str]
|
||||
usage: PlanUsageResponse
|
||||
|
||||
@@ -7,7 +7,11 @@ from app.features.plan.catalog import (
|
||||
get_plan_by_type,
|
||||
get_plans_for_api,
|
||||
)
|
||||
from app.features.plan.schemas import CurrentPlanResponse, PlanResponse
|
||||
from app.features.plan.schemas import (
|
||||
CurrentPlanResponse,
|
||||
PlanResponse,
|
||||
PlanUsageResponse,
|
||||
)
|
||||
from app.features.quota.service import QuotaService
|
||||
from app.features.user.models import User
|
||||
|
||||
@@ -32,12 +36,12 @@ class PlanService:
|
||||
async def get_current_plan_response(self, user: User) -> CurrentPlanResponse:
|
||||
plan = get_plan_by_type(user.subscription_type)
|
||||
segment_count, chapter_count = await self._quota.get_usage(user.id)
|
||||
usage = {
|
||||
"conversations": segment_count,
|
||||
"chapters": chapter_count,
|
||||
"max_conversations": plan.max_conversations,
|
||||
"max_chapters": plan.max_chapters,
|
||||
}
|
||||
usage = PlanUsageResponse(
|
||||
conversations=segment_count,
|
||||
chapters=chapter_count,
|
||||
max_conversations=plan.max_conversations,
|
||||
max_chapters=plan.max_chapters,
|
||||
)
|
||||
expires_at = None
|
||||
if user.subscription_expires_at:
|
||||
expires_at = user.subscription_expires_at.isoformat()
|
||||
|
||||
Reference in New Issue
Block a user