1. 为渠道账户ID查询添加最小长度校验,统一分析模块常量引用 2. 新增扫码登录向导端点,完善文档说明 3. 优化配对统计接口,移除无效参数 4. 为出站箱接口添加批量上限与202状态码 5. 新增测试用例、访问规则、配额等模块的查询与校验参数 6. 新增适配器健康批量查询、健康检查触发接口 7. 统一告警、审计日志的错误处理方式 8. 新增插件配置账户ID支持,优化批量操作响应 9. 新增环境健康批量查询、Webhook限流与参数校验 10. 完善会话管理、审计日志的参数与文档说明 11. 修复导入模块的校验错误处理逻辑
572 lines
23 KiB
Python
572 lines
23 KiB
Python
"""Quota 子域 Router。
|
||
|
||
外部系统限界上下文的配额管理 API,覆盖配额列表 / 详情 / 创建 / 更新 /
|
||
阈值查询 / 阈值设置 / 删除,以及扩展能力:按业务键查询 / 接近告警阈值列表 /
|
||
接近临界阈值列表 / 即将重置列表 / 统计聚合 / 重置窗口 / 批量调整 /
|
||
创建或更新 / 按系统批量删除。所有端点通过 ``create_use_cases_from_db`` 装配
|
||
use_cases,经 ``quota_service`` 端口调用用例。
|
||
|
||
Request Schema 与 Input DTO 不共享类,Router 内显式构造 DTO。
|
||
|
||
路径顺序约束:静态路径(``/thresholds`` / ``/by-key`` / ``/near-warning`` /
|
||
``/near-critical`` / ``/expiring`` / ``/stats`` / ``/batch-adjust`` /
|
||
``/upsert`` / ``/by-system/{system_id}``)必须在 ``/{quota_id}`` 前声明,
|
||
避免被路径参数捕获。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from datetime import datetime
|
||
from typing import Any, Literal
|
||
|
||
from fastapi import APIRouter, Depends, Path, Query
|
||
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
from yuxi.external_systems.infrastructure.container import create_use_cases_from_db
|
||
from yuxi.external_systems.use_cases.dto.quota import (
|
||
BatchAdjustQuotaInput,
|
||
BatchDeleteQuotaInput,
|
||
CreateQuotaInput,
|
||
DeleteQuotaInput,
|
||
DeleteQuotasBySystemInput,
|
||
GetQuotaByKeyInput,
|
||
GetQuotaInput,
|
||
GetQuotaStatsInput,
|
||
GetQuotaThresholdInput,
|
||
ListExpiringInput,
|
||
ListNearCriticalInput,
|
||
ListNearWarningInput,
|
||
ListQuotasInput,
|
||
QuotaAdjustmentItem,
|
||
ResetQuotaInput,
|
||
SetQuotaThresholdInput,
|
||
UpdateQuotaInput,
|
||
UpsertQuotaInput,
|
||
)
|
||
from yuxi.storage.postgres.models_business import User
|
||
|
||
from server.utils.auth_middleware import get_admin_user, get_db, get_required_user
|
||
|
||
quota_router = APIRouter(prefix="/quotas", tags=["external-systems-quota"])
|
||
|
||
|
||
# ---------------- Request Schemas ----------------
|
||
|
||
QuotaWindowType = Literal["daily", "hourly", "rolling_24h", "minute", "custom"]
|
||
SourceHeaderStrategy = Literal["remaining", "used", "limit", "fraction_used_limit"]
|
||
|
||
|
||
def _validate_thresholds(
|
||
warning: int | None,
|
||
critical: int | None,
|
||
) -> None:
|
||
"""校验告警阈值不变量:``warning_threshold <= critical_threshold``。
|
||
|
||
仅在两者均显式传入时校验;部分更新场景(仅传其一)由 Service 层
|
||
读取现有值后补全校验。
|
||
"""
|
||
if warning is not None and critical is not None and warning > critical:
|
||
raise ValueError(f"warning_threshold({warning}) 不能大于 critical_threshold({critical})")
|
||
|
||
|
||
class CreateQuotaRequest(BaseModel):
|
||
"""创建配额请求体。字段对齐 ``CreateQuotaInput``。
|
||
|
||
字符串字段长度约束对齐 ``ExternalSystemQuotaUsage`` ORM 列定义,在边界层
|
||
拦截非法输入;``window_start``/``window_end`` 使用 datetime 类型,由
|
||
Pydantic 解析 ISO 8601 字符串;阈值字段约束 0-100,且
|
||
``warning_threshold <= critical_threshold``。
|
||
"""
|
||
|
||
model_config = ConfigDict(frozen=True)
|
||
|
||
system_id: int
|
||
quota_key: str = Field(..., min_length=1, max_length=128)
|
||
quota_name: str = Field(..., min_length=1, max_length=128)
|
||
quota_window: QuotaWindowType = Field(..., description="窗口类型")
|
||
env_key: str = Field(default="default", min_length=1, max_length=32)
|
||
window_start: datetime | None = None
|
||
window_end: datetime | None = None
|
||
limit_value: int = Field(default=0, ge=0)
|
||
used_value: int = Field(default=0, ge=0)
|
||
unit: str = Field(default="calls", min_length=1, max_length=32)
|
||
source: Literal["response_header", "manual", "config"] = Field(default="config")
|
||
source_header_name: str | None = Field(default=None, max_length=128)
|
||
source_header_strategy: SourceHeaderStrategy = Field(
|
||
default="remaining",
|
||
description="响应头解析策略:remaining/used/limit/fraction_used_limit",
|
||
)
|
||
warning_threshold: int = Field(default=80, ge=0, le=100)
|
||
critical_threshold: int = Field(default=95, ge=0, le=100)
|
||
|
||
@model_validator(mode="after")
|
||
def _check_thresholds(self) -> CreateQuotaRequest:
|
||
_validate_thresholds(self.warning_threshold, self.critical_threshold)
|
||
return self
|
||
|
||
|
||
class SetQuotaThresholdRequest(BaseModel):
|
||
"""设置配额阈值请求体。字段对齐 ``SetQuotaThresholdInput``(不含 id)。
|
||
|
||
仅透传客户端显式设置的字段。两者均传入时校验
|
||
``warning_threshold <= critical_threshold``;仅传其一由 Service 层
|
||
读取现有值后补全校验。
|
||
"""
|
||
|
||
model_config = ConfigDict(frozen=True)
|
||
|
||
warning_threshold: int | None = Field(default=None, ge=0, le=100)
|
||
critical_threshold: int | None = Field(default=None, ge=0, le=100)
|
||
|
||
@model_validator(mode="after")
|
||
def _check_thresholds(self) -> SetQuotaThresholdRequest:
|
||
_validate_thresholds(self.warning_threshold, self.critical_threshold)
|
||
return self
|
||
|
||
|
||
class UpdateQuotaRequest(BaseModel):
|
||
"""更新配额请求体。字段对齐 ``UpdateQuotaInput``(不含 id)。
|
||
|
||
所有字段可选,支持部分更新;约束与 ``CreateQuotaRequest`` 保持一致,
|
||
在边界层拦截非法输入。仅透传客户端显式设置的字段。两者均传入时校验
|
||
``warning_threshold <= critical_threshold``;仅传其一由 Service 层
|
||
读取现有值后补全校验。
|
||
"""
|
||
|
||
model_config = ConfigDict(frozen=True)
|
||
|
||
quota_name: str | None = Field(default=None, min_length=1, max_length=128)
|
||
quota_window: QuotaWindowType | None = Field(default=None, description="窗口类型")
|
||
limit_value: int | None = Field(default=None, ge=0)
|
||
used_value: int | None = Field(default=None, ge=0)
|
||
unit: str | None = Field(default=None, min_length=1, max_length=32)
|
||
source: Literal["response_header", "manual", "config"] | None = Field(default=None)
|
||
source_header_name: str | None = Field(default=None, max_length=128)
|
||
source_header_strategy: SourceHeaderStrategy | None = Field(
|
||
default=None,
|
||
description="响应头解析策略:remaining/used/limit/fraction_used_limit",
|
||
)
|
||
warning_threshold: int | None = Field(default=None, ge=0, le=100)
|
||
critical_threshold: int | None = Field(default=None, ge=0, le=100)
|
||
window_start: datetime | None = None
|
||
window_end: datetime | None = None
|
||
|
||
@model_validator(mode="after")
|
||
def _check_thresholds(self) -> UpdateQuotaRequest:
|
||
_validate_thresholds(self.warning_threshold, self.critical_threshold)
|
||
return self
|
||
|
||
|
||
class ResetQuotaRequest(BaseModel):
|
||
"""重置配额窗口请求体。字段对齐 ``ResetQuotaInput``(不含 id)。
|
||
|
||
允许仅重置 ``used_value``(如把用量清零但保持当前窗口),也允许同时更新
|
||
窗口范围。传入窗口范围时校验 ``window_start < window_end``。
|
||
"""
|
||
|
||
model_config = ConfigDict(frozen=True)
|
||
|
||
window_start: datetime | None = None
|
||
window_end: datetime | None = None
|
||
used_value: int | None = Field(default=None, ge=0)
|
||
|
||
@model_validator(mode="after")
|
||
def _check_window_range(self) -> ResetQuotaRequest:
|
||
if self.window_start is not None and self.window_end is not None and self.window_start >= self.window_end:
|
||
raise ValueError("window_start 必须早于 window_end")
|
||
return self
|
||
|
||
|
||
class QuotaAdjustmentItemRequest(BaseModel):
|
||
"""批量调整项请求体(对齐 ``QuotaAdjustmentItem`` DTO)。"""
|
||
|
||
model_config = ConfigDict(frozen=True)
|
||
|
||
quota_id: int = Field(..., ge=1)
|
||
limit_value: int = Field(..., ge=0)
|
||
reason: str | None = Field(default=None, max_length=500)
|
||
|
||
|
||
class BatchAdjustQuotaRequest(BaseModel):
|
||
"""批量调整配额限额请求体。"""
|
||
|
||
model_config = ConfigDict(frozen=True)
|
||
|
||
adjustments: list[QuotaAdjustmentItemRequest] = Field(..., min_length=1, max_length=100)
|
||
|
||
|
||
class BatchDeleteQuotaRequest(BaseModel):
|
||
"""批量删除配额请求体。"""
|
||
|
||
model_config = ConfigDict(frozen=True)
|
||
|
||
ids: list[int] = Field(..., min_length=1, max_length=100)
|
||
|
||
|
||
class UpsertQuotaRequest(BaseModel):
|
||
"""创建或更新配额请求体。字段对齐 ``UpsertQuotaInput``。"""
|
||
|
||
model_config = ConfigDict(frozen=True)
|
||
|
||
system_id: int
|
||
quota_key: str = Field(..., min_length=1, max_length=128)
|
||
quota_name: str = Field(..., min_length=1, max_length=128)
|
||
quota_window: QuotaWindowType = Field(..., description="窗口类型")
|
||
env_key: str = Field(default="default", min_length=1, max_length=32)
|
||
window_start: datetime | None = None
|
||
window_end: datetime | None = None
|
||
limit_value: int = Field(default=0, ge=0)
|
||
used_value: int = Field(default=0, ge=0)
|
||
unit: str = Field(default="calls", min_length=1, max_length=32)
|
||
source: Literal["response_header", "manual", "config"] = Field(default="config")
|
||
source_header_name: str | None = Field(default=None, max_length=128)
|
||
source_header_strategy: SourceHeaderStrategy = Field(
|
||
default="remaining",
|
||
description="响应头解析策略:remaining/used/limit/fraction_used_limit",
|
||
)
|
||
warning_threshold: int = Field(default=80, ge=0, le=100)
|
||
critical_threshold: int = Field(default=95, ge=0, le=100)
|
||
|
||
@model_validator(mode="after")
|
||
def _check_thresholds(self) -> UpsertQuotaRequest:
|
||
_validate_thresholds(self.warning_threshold, self.critical_threshold)
|
||
return self
|
||
|
||
|
||
# ---------------- Endpoints ----------------
|
||
|
||
|
||
# ---- 静态路径端点(必须在 /{quota_id} 之前声明) ----
|
||
|
||
|
||
@quota_router.get("/thresholds", response_model=dict)
|
||
async def get_quota_threshold(
|
||
system_id: int = Query(...),
|
||
env_key: str | None = Query(None, max_length=32),
|
||
quota_key: str | None = Query(None, max_length=128),
|
||
limit: int = Query(100, ge=1, le=500, description="返回记录数上限"),
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_required_user),
|
||
) -> dict[str, Any]:
|
||
"""获取达到告警阈值的配额列表(含临界)。
|
||
|
||
``system_id`` 为必填过滤维度,对齐 ``GetQuotaThresholdInput`` 契约。
|
||
"""
|
||
use_cases = create_use_cases_from_db(db)
|
||
input_dto = GetQuotaThresholdInput(
|
||
system_id=system_id,
|
||
env_key=env_key,
|
||
quota_key=quota_key,
|
||
limit=limit,
|
||
)
|
||
output = await use_cases.quota_service.get_quota_threshold(input_dto)
|
||
return {"success": True, "data": output.model_dump()}
|
||
|
||
|
||
@quota_router.get("/by-key", response_model=dict)
|
||
async def get_quota_by_key(
|
||
system_id: int = Query(...),
|
||
env_key: str = Query("default", max_length=32),
|
||
quota_key: str = Query(..., max_length=128),
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_required_user),
|
||
) -> dict[str, Any]:
|
||
"""按业务键查询配额。无数据返回 null。
|
||
|
||
探测性查询语义:配额可能尚未配置,调用方需自行处理 null。与按 ID
|
||
查询(``GET /{quota_id}``,不存在抛 404)的语义不同。
|
||
"""
|
||
use_cases = create_use_cases_from_db(db)
|
||
input_dto = GetQuotaByKeyInput(
|
||
system_id=system_id,
|
||
env_key=env_key,
|
||
quota_key=quota_key,
|
||
)
|
||
output = await use_cases.quota_service.get_quota_by_key(input_dto)
|
||
return {"success": True, "data": output.model_dump() if output else None}
|
||
|
||
|
||
@quota_router.get("/near-warning", response_model=dict)
|
||
async def list_near_warning(
|
||
limit: int = Query(100, ge=1, le=500, description="返回记录数上限"),
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_required_user),
|
||
) -> dict[str, Any]:
|
||
"""列出接近告警阈值的配额(全局,含临界)。
|
||
|
||
与 ``GET /thresholds`` 的区别:本端点为全局查询(无 ``system_id``
|
||
过滤),用于跨系统告警大盘。
|
||
"""
|
||
use_cases = create_use_cases_from_db(db)
|
||
input_dto = ListNearWarningInput(limit=limit)
|
||
output = await use_cases.quota_service.list_near_warning(input_dto)
|
||
return {"success": True, "data": output.model_dump()}
|
||
|
||
|
||
@quota_router.get("/near-critical", response_model=dict)
|
||
async def list_near_critical(
|
||
limit: int = Query(100, ge=1, le=500, description="返回记录数上限"),
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_required_user),
|
||
) -> dict[str, Any]:
|
||
"""列出接近临界阈值的配额(全局,无 ``system_id`` 过滤)。"""
|
||
use_cases = create_use_cases_from_db(db)
|
||
input_dto = ListNearCriticalInput(limit=limit)
|
||
output = await use_cases.quota_service.list_near_critical(input_dto)
|
||
return {"success": True, "data": output.model_dump()}
|
||
|
||
|
||
@quota_router.get("/expiring", response_model=dict)
|
||
async def list_expiring(
|
||
before: datetime = Query(..., description="ISO8601 截止时间,返回 window_end <= before 的配额"),
|
||
system_id: int | None = Query(None, description="按系统过滤(可选)"),
|
||
limit: int = Query(100, ge=1, le=500, description="返回记录数上限"),
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_required_user),
|
||
) -> dict[str, Any]:
|
||
"""列出即将重置的配额(``window_end <= before``)。"""
|
||
use_cases = create_use_cases_from_db(db)
|
||
input_dto = ListExpiringInput(
|
||
before=before,
|
||
system_id=system_id,
|
||
limit=limit,
|
||
)
|
||
output = await use_cases.quota_service.list_expiring(input_dto)
|
||
return {"success": True, "data": output.model_dump()}
|
||
|
||
|
||
@quota_router.get("/stats", response_model=dict)
|
||
async def get_quota_stats(
|
||
system_id: int | None = Query(None, description="按系统过滤(可选,不传则全局聚合)"),
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_required_user),
|
||
) -> dict[str, Any]:
|
||
"""配额统计聚合。
|
||
|
||
所有字段均为精确值,统一复用仓储 ``count`` 方法(支持 ``system_id``
|
||
过滤):``total`` / ``by_window`` / ``near_warning_count`` /
|
||
``near_critical_count`` / ``expiring_count``(窗口已结束,需重置)。
|
||
"""
|
||
use_cases = create_use_cases_from_db(db)
|
||
input_dto = GetQuotaStatsInput(system_id=system_id)
|
||
output = await use_cases.quota_service.get_quota_stats(input_dto)
|
||
return {"success": True, "data": output.model_dump()}
|
||
|
||
|
||
@quota_router.post("/batch-adjust", response_model=dict)
|
||
async def batch_adjust_quotas(
|
||
payload: BatchAdjustQuotaRequest,
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_admin_user),
|
||
) -> dict[str, Any]:
|
||
"""批量调整配额限额。``updated_by`` 由 ``current_user.uid`` 填充
|
||
(对齐 ``batch_enable_environments``,批量更新操作使用 ``updated_by``)。"""
|
||
use_cases = create_use_cases_from_db(db)
|
||
input_dto = BatchAdjustQuotaInput(
|
||
adjustments=[
|
||
QuotaAdjustmentItem(
|
||
quota_id=item.quota_id,
|
||
limit_value=item.limit_value,
|
||
reason=item.reason,
|
||
)
|
||
for item in payload.adjustments
|
||
],
|
||
updated_by=current_user.uid,
|
||
)
|
||
output = await use_cases.quota_service.batch_adjust_quotas(input_dto)
|
||
return {"success": True, "data": output.model_dump()}
|
||
|
||
|
||
@quota_router.delete("/batch", response_model=dict)
|
||
async def batch_delete_quotas(
|
||
payload: BatchDeleteQuotaRequest,
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_admin_user),
|
||
) -> dict[str, Any]:
|
||
"""批量删除配额。``user`` 由 ``current_user.uid`` 填充。
|
||
|
||
部分成功语义:信封 ``success`` 固定为 ``True``(表示请求已处理),
|
||
操作级结果(``deleted_count`` / ``failed_count`` / ``failures``)在
|
||
``data`` 内返回,对齐 ``batch_adjust_quotas`` 及其他批量端点约定。
|
||
"""
|
||
use_cases = create_use_cases_from_db(db)
|
||
input_dto = BatchDeleteQuotaInput(
|
||
ids=payload.ids,
|
||
user=current_user.uid,
|
||
)
|
||
output = await use_cases.quota_service.batch_delete_quotas(input_dto)
|
||
return {"success": True, "data": output.model_dump()}
|
||
|
||
|
||
@quota_router.post("/upsert", response_model=dict)
|
||
async def upsert_quota(
|
||
payload: UpsertQuotaRequest,
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_admin_user),
|
||
) -> dict[str, Any]:
|
||
"""创建或更新配额(基于 ``system_id+env_key+quota_key`` 冲突时更新)。
|
||
|
||
``created_by`` / ``updated_by`` 均由 ``current_user.uid`` 填充:
|
||
- 新建场景:``created_by`` 生效,``updated_by`` 同步填充以便首次审计完整。
|
||
- 更新场景:``updated_by`` 生效,``created_by`` 由仓储层忽略原值保持不变。
|
||
"""
|
||
use_cases = create_use_cases_from_db(db)
|
||
input_dto = UpsertQuotaInput(
|
||
**payload.model_dump(),
|
||
created_by=current_user.uid,
|
||
updated_by=current_user.uid,
|
||
)
|
||
output = await use_cases.quota_service.upsert_quota(input_dto)
|
||
return {"success": True, "data": output.model_dump()}
|
||
|
||
|
||
@quota_router.delete("/by-system/{system_id}", response_model=dict)
|
||
async def delete_quotas_by_system(
|
||
system_id: int = Path(ge=1),
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_admin_user),
|
||
) -> dict[str, Any]:
|
||
"""按系统批量软删除配额。"""
|
||
use_cases = create_use_cases_from_db(db)
|
||
input_dto = DeleteQuotasBySystemInput(system_id=system_id, user=current_user.uid)
|
||
output = await use_cases.quota_service.delete_quotas_by_system(input_dto)
|
||
return {"success": True, "data": output.model_dump()}
|
||
|
||
|
||
# ---- 根路径端点 ----
|
||
|
||
|
||
@quota_router.get("", response_model=dict)
|
||
async def list_quotas(
|
||
limit: int = Query(100, ge=1, le=500, description="返回记录数上限"),
|
||
offset: int = Query(0, ge=0, description="偏移量(与 limit 配合分页)"),
|
||
system_id: int | None = Query(None, description="按系统过滤"),
|
||
env_key: str | None = Query(None, max_length=32, description="按环境键过滤"),
|
||
quota_key: str | None = Query(None, max_length=128, description="按配额键过滤"),
|
||
quota_window: Literal["daily", "hourly", "rolling_24h", "minute", "custom"] | None = Query(
|
||
None, description="窗口类型:daily/hourly/rolling_24h/minute/custom"
|
||
),
|
||
source: Literal["response_header", "manual", "config"] | None = Query(
|
||
None, description="配额来源:response_header/manual/config"
|
||
),
|
||
near_warning: bool = Query(False, description="仅返回接近告警阈值的配额"),
|
||
near_critical: bool = Query(False, description="仅返回接近临界阈值的配额"),
|
||
expiring_before: datetime | None = Query(None, description="仅返回 window_end <= 该时间的配额"),
|
||
keyword: str | None = Query(None, max_length=128, description="按 quota_name / quota_key 模糊匹配"),
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_required_user),
|
||
) -> dict[str, Any]:
|
||
"""分页列出配额。支持多维度过滤。"""
|
||
use_cases = create_use_cases_from_db(db)
|
||
input_dto = ListQuotasInput(
|
||
limit=limit,
|
||
offset=offset,
|
||
system_id=system_id,
|
||
env_key=env_key,
|
||
quota_key=quota_key,
|
||
quota_window=quota_window,
|
||
source=source,
|
||
near_warning=near_warning,
|
||
near_critical=near_critical,
|
||
expiring_before=expiring_before,
|
||
keyword=keyword,
|
||
)
|
||
output = await use_cases.quota_service.list_quotas(input_dto)
|
||
return {"success": True, "data": output.model_dump()}
|
||
|
||
|
||
@quota_router.post("", response_model=dict)
|
||
async def create_quota(
|
||
payload: CreateQuotaRequest,
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_admin_user),
|
||
) -> dict[str, Any]:
|
||
"""创建配额。``created_by`` 由 ``current_user.uid`` 填充。"""
|
||
use_cases = create_use_cases_from_db(db)
|
||
input_dto = CreateQuotaInput(**payload.model_dump(), created_by=current_user.uid)
|
||
output = await use_cases.quota_service.create_quota(input_dto)
|
||
return {"success": True, "data": output.model_dump()}
|
||
|
||
|
||
# ---- 动态路径端点 /{quota_id} ----
|
||
|
||
|
||
@quota_router.get("/{quota_id}", response_model=dict)
|
||
async def get_quota(
|
||
quota_id: int = Path(ge=1),
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_required_user),
|
||
) -> dict[str, Any]:
|
||
"""获取配额详情。"""
|
||
use_cases = create_use_cases_from_db(db)
|
||
input_dto = GetQuotaInput(id=quota_id)
|
||
output = await use_cases.quota_service.get_quota(input_dto)
|
||
return {"success": True, "data": output.model_dump()}
|
||
|
||
|
||
@quota_router.put("/{quota_id}", response_model=dict)
|
||
async def update_quota(
|
||
payload: UpdateQuotaRequest,
|
||
quota_id: int = Path(ge=1),
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_admin_user),
|
||
) -> dict[str, Any]:
|
||
"""更新配额(部分更新)。仅透传显式设置的字段。``updated_by`` 由 ``current_user.uid`` 填充。"""
|
||
use_cases = create_use_cases_from_db(db)
|
||
input_dto = UpdateQuotaInput(
|
||
id=quota_id,
|
||
**payload.model_dump(exclude_unset=True),
|
||
updated_by=current_user.uid,
|
||
)
|
||
output = await use_cases.quota_service.update_quota(input_dto)
|
||
return {"success": True, "data": output.model_dump()}
|
||
|
||
|
||
@quota_router.patch("/{quota_id}/threshold", response_model=dict)
|
||
async def set_quota_threshold(
|
||
payload: SetQuotaThresholdRequest,
|
||
quota_id: int = Path(ge=1),
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_admin_user),
|
||
) -> dict[str, Any]:
|
||
"""设置配额阈值。仅透传显式设置的字段。``updated_by`` 由 ``current_user.uid`` 填充。"""
|
||
use_cases = create_use_cases_from_db(db)
|
||
input_dto = SetQuotaThresholdInput(
|
||
id=quota_id,
|
||
**payload.model_dump(exclude_unset=True),
|
||
updated_by=current_user.uid,
|
||
)
|
||
output = await use_cases.quota_service.set_quota_threshold(input_dto)
|
||
return {"success": True, "data": output.model_dump()}
|
||
|
||
|
||
@quota_router.post("/{quota_id}/reset", response_model=dict)
|
||
async def reset_quota(
|
||
payload: ResetQuotaRequest,
|
||
quota_id: int = Path(ge=1),
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_admin_user),
|
||
) -> dict[str, Any]:
|
||
"""重置配额窗口。仅透传显式设置的字段。``updated_by`` 由 ``current_user.uid`` 填充。"""
|
||
use_cases = create_use_cases_from_db(db)
|
||
input_dto = ResetQuotaInput(
|
||
id=quota_id,
|
||
**payload.model_dump(exclude_unset=True),
|
||
updated_by=current_user.uid,
|
||
)
|
||
output = await use_cases.quota_service.reset_quota(input_dto)
|
||
return {"success": True, "data": output.model_dump()}
|
||
|
||
|
||
@quota_router.delete("/{quota_id}", response_model=dict)
|
||
async def delete_quota(
|
||
quota_id: int = Path(ge=1),
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_admin_user),
|
||
) -> dict[str, Any]:
|
||
"""删除配额。``user`` 由 ``current_user.uid`` 填充。"""
|
||
use_cases = create_use_cases_from_db(db)
|
||
input_dto = DeleteQuotaInput(id=quota_id, user=current_user.uid)
|
||
output = await use_cases.quota_service.delete_quota(input_dto)
|
||
return {"success": True, "data": output.model_dump()}
|