mirror of
https://github.com/fawney19/Aether.git
synced 2026-01-03 00:02:28 +08:00
654 lines
21 KiB
Python
654 lines
21 KiB
Python
"""
|
||
API端点请求/响应模型定义
|
||
"""
|
||
|
||
import re
|
||
from datetime import datetime
|
||
from typing import Any, Dict, List, Optional
|
||
|
||
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
||
|
||
from ..core.enums import UserRole
|
||
|
||
|
||
# ========== 认证相关 ==========
|
||
class LoginRequest(BaseModel):
|
||
"""登录请求"""
|
||
|
||
email: str = Field(..., min_length=3, max_length=255, description="邮箱地址")
|
||
password: str = Field(..., min_length=1, max_length=128, description="密码")
|
||
|
||
@classmethod
|
||
@field_validator("email")
|
||
def validate_email(cls, v):
|
||
"""验证邮箱格式"""
|
||
email_pattern = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
|
||
if not re.match(email_pattern, v):
|
||
raise ValueError("邮箱格式无效")
|
||
return v.lower()
|
||
|
||
@classmethod
|
||
@field_validator("password")
|
||
def validate_password(cls, v):
|
||
"""验证密码不为空且去除前后空格"""
|
||
v = v.strip()
|
||
if not v:
|
||
raise ValueError("密码不能为空")
|
||
return v
|
||
|
||
|
||
class LoginResponse(BaseModel):
|
||
"""登录响应"""
|
||
|
||
access_token: str
|
||
refresh_token: str # 刷新令牌
|
||
token_type: str = "bearer"
|
||
expires_in: int = 86400 # Token有效期(秒),默认24小时
|
||
user_id: str
|
||
email: str
|
||
username: str
|
||
role: str
|
||
|
||
|
||
class RefreshTokenRequest(BaseModel):
|
||
"""刷新令牌请求"""
|
||
|
||
refresh_token: str = Field(..., description="刷新令牌")
|
||
|
||
|
||
class RefreshTokenResponse(BaseModel):
|
||
"""刷新令牌响应"""
|
||
|
||
access_token: str
|
||
refresh_token: str # 返回新的刷新令牌
|
||
token_type: str = "bearer"
|
||
expires_in: int = 86400 # Token有效期(秒),默认24小时
|
||
|
||
|
||
class RegisterRequest(BaseModel):
|
||
"""注册请求"""
|
||
|
||
email: str = Field(..., min_length=3, max_length=255, description="邮箱地址")
|
||
username: str = Field(..., min_length=2, max_length=50, description="用户名")
|
||
password: str = Field(..., min_length=6, max_length=128, description="密码")
|
||
|
||
@classmethod
|
||
@field_validator("email")
|
||
def validate_email(cls, v):
|
||
"""验证邮箱格式"""
|
||
email_pattern = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
|
||
if not re.match(email_pattern, v):
|
||
raise ValueError("邮箱格式无效")
|
||
return v.lower()
|
||
|
||
@classmethod
|
||
@field_validator("username")
|
||
def validate_username(cls, v):
|
||
"""验证用户名格式"""
|
||
v = v.strip()
|
||
if not v:
|
||
raise ValueError("用户名不能为空")
|
||
if not re.match(r"^[a-zA-Z0-9_-]+$", v):
|
||
raise ValueError("用户名只能包含字母、数字、下划线和短横线")
|
||
return v
|
||
|
||
@classmethod
|
||
@field_validator("password")
|
||
def validate_password(cls, v):
|
||
"""验证密码强度"""
|
||
if len(v) < 6:
|
||
raise ValueError("密码至少需要6个字符")
|
||
if not re.search(r"[A-Z]", v):
|
||
raise ValueError("密码必须包含至少一个大写字母")
|
||
if not re.search(r"[a-z]", v):
|
||
raise ValueError("密码必须包含至少一个小写字母")
|
||
if not re.search(r"\d", v):
|
||
raise ValueError("密码必须包含至少一个数字")
|
||
return v
|
||
|
||
|
||
class RegisterResponse(BaseModel):
|
||
"""注册响应"""
|
||
|
||
user_id: str
|
||
email: str
|
||
username: str
|
||
message: str
|
||
|
||
|
||
class LogoutResponse(BaseModel):
|
||
"""登出响应"""
|
||
|
||
message: str
|
||
success: bool
|
||
|
||
|
||
# ========== 用户管理 ==========
|
||
class CreateUserRequest(BaseModel):
|
||
"""创建用户请求"""
|
||
|
||
username: str = Field(..., min_length=2, max_length=50, description="用户名")
|
||
password: str = Field(..., min_length=6, max_length=128, description="密码")
|
||
email: str = Field(..., min_length=3, max_length=255, description="邮箱地址")
|
||
role: Optional[UserRole] = Field(UserRole.USER, description="用户角色")
|
||
quota_usd: Optional[float] = Field(default=10.0, description="USD配额,null表示无限制")
|
||
|
||
@field_validator("quota_usd", mode="before")
|
||
@classmethod
|
||
def validate_quota_usd(cls, v):
|
||
"""验证配额值,允许null表示无限制"""
|
||
if v is None:
|
||
return None
|
||
if isinstance(v, (int, float)) and v >= 0 and v <= 10000:
|
||
return float(v)
|
||
if isinstance(v, (int, float)):
|
||
raise ValueError("配额必须在 0-10000 范围内")
|
||
return v
|
||
|
||
@classmethod
|
||
@field_validator("email")
|
||
def validate_email(cls, v):
|
||
"""验证邮箱格式"""
|
||
v = v.strip()
|
||
if not v:
|
||
raise ValueError("邮箱不能为空")
|
||
email_pattern = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
|
||
if not re.match(email_pattern, v):
|
||
raise ValueError("邮箱格式无效")
|
||
return v.lower()
|
||
|
||
@classmethod
|
||
@field_validator("username")
|
||
def validate_username(cls, v):
|
||
"""验证用户名格式"""
|
||
v = v.strip()
|
||
if not v:
|
||
raise ValueError("用户名不能为空")
|
||
if not re.match(r"^[a-zA-Z0-9_-]+$", v):
|
||
raise ValueError("用户名只能包含字母、数字、下划线和短横线")
|
||
return v
|
||
|
||
@classmethod
|
||
@field_validator("password")
|
||
def validate_password(cls, v):
|
||
"""验证密码强度"""
|
||
if len(v) < 6:
|
||
raise ValueError("密码至少需要6个字符")
|
||
if not re.search(r"[A-Z]", v):
|
||
raise ValueError("密码必须包含至少一个大写字母")
|
||
if not re.search(r"[a-z]", v):
|
||
raise ValueError("密码必须包含至少一个小写字母")
|
||
if not re.search(r"\d", v):
|
||
raise ValueError("密码必须包含至少一个数字")
|
||
return v
|
||
|
||
|
||
class UpdateUserRequest(BaseModel):
|
||
"""更新用户请求"""
|
||
|
||
email: Optional[str] = None
|
||
username: Optional[str] = None
|
||
password: Optional[str] = None
|
||
role: Optional[UserRole] = None
|
||
allowed_providers: Optional[List[str]] = None # 允许使用的提供商 ID 列表
|
||
allowed_endpoints: Optional[List[str]] = None # 允许使用的端点 ID 列表
|
||
allowed_models: Optional[List[str]] = None # 允许使用的模型名称列表
|
||
quota_usd: Optional[float] = None
|
||
is_active: Optional[bool] = None
|
||
|
||
@field_validator("quota_usd", mode="before")
|
||
@classmethod
|
||
def validate_quota_usd(cls, v):
|
||
"""验证配额值,允许null表示无限制"""
|
||
if v is None:
|
||
return None
|
||
if isinstance(v, (int, float)) and v >= 0 and v <= 10000:
|
||
return float(v)
|
||
if isinstance(v, (int, float)):
|
||
raise ValueError("配额必须在 0-10000 范围内")
|
||
return v
|
||
|
||
|
||
class CreateApiKeyRequest(BaseModel):
|
||
"""创建API密钥请求"""
|
||
|
||
name: Optional[str] = None
|
||
allowed_providers: Optional[List[str]] = None # 允许使用的提供商 ID 列表
|
||
allowed_endpoints: Optional[List[str]] = None # 允许使用的端点 ID 列表
|
||
allowed_api_formats: Optional[List[str]] = None # 允许使用的 API 格式列表
|
||
allowed_models: Optional[List[str]] = None # 允许使用的模型名称列表
|
||
rate_limit: Optional[int] = 100
|
||
expire_days: Optional[int] = None # None = 永不过期,数字 = 多少天后过期
|
||
initial_balance_usd: Optional[float] = Field(
|
||
None, description="初始余额(USD),仅用于独立Key,None = 无限制"
|
||
)
|
||
is_standalone: bool = Field(False, description="是否为独立余额Key(给非注册用户使用)")
|
||
auto_delete_on_expiry: bool = Field(
|
||
False, description="过期后是否自动删除(True=物理删除,False=仅禁用)"
|
||
)
|
||
|
||
|
||
class UserResponse(BaseModel):
|
||
"""用户响应"""
|
||
|
||
id: str
|
||
email: str
|
||
username: str
|
||
role: UserRole
|
||
allowed_providers: Optional[List[str]] = None # 允许使用的提供商 ID 列表
|
||
allowed_endpoints: Optional[List[str]] = None # 允许使用的端点 ID 列表
|
||
allowed_models: Optional[List[str]] = None # 允许使用的模型名称列表
|
||
quota_usd: float
|
||
used_usd: float
|
||
is_active: bool
|
||
created_at: datetime
|
||
updated_at: datetime
|
||
last_login_at: Optional[datetime]
|
||
|
||
|
||
class ApiKeyResponse(BaseModel):
|
||
"""API密钥响应"""
|
||
|
||
id: str
|
||
user_id: str
|
||
key: Optional[str] = None # 仅在创建时返回完整密钥
|
||
key_display: Optional[str] = None # 脱敏后的密钥显示
|
||
name: Optional[str]
|
||
total_requests: int
|
||
total_tokens: int
|
||
total_cost_usd: float
|
||
allowed_providers: Optional[List[str]]
|
||
allowed_models: Optional[List[str]]
|
||
rate_limit: int
|
||
is_active: bool
|
||
expires_at: Optional[datetime] = None
|
||
balance_used_usd: float = 0.0
|
||
current_balance_usd: Optional[float] = None # NULL = 无限制
|
||
is_standalone: bool = False
|
||
force_capabilities: Optional[Dict[str, bool]] = None # 强制开启的能力
|
||
created_at: datetime
|
||
last_used_at: Optional[datetime]
|
||
|
||
|
||
# ========== 提供商管理 ==========
|
||
class ProviderCreate(BaseModel):
|
||
"""创建提供商请求
|
||
|
||
新架构说明:
|
||
- Provider 仅包含提供商的元数据和计费配置
|
||
- API格式、URL、认证等配置应在 ProviderEndpoint 中设置
|
||
- API密钥应在 ProviderAPIKey 中设置
|
||
"""
|
||
|
||
name: str = Field(..., min_length=1, max_length=100, description="提供商唯一标识")
|
||
display_name: str = Field(..., min_length=1, max_length=100, description="显示名称")
|
||
description: Optional[str] = Field(None, description="提供商描述")
|
||
website: Optional[str] = Field(None, max_length=500, description="主站网站")
|
||
|
||
# Provider 级别的配置
|
||
rate_limit: Optional[int] = Field(None, description="每分钟请求限制")
|
||
concurrent_limit: Optional[int] = Field(None, description="并发请求限制")
|
||
config: Optional[dict] = Field(None, description="额外配置")
|
||
is_active: bool = Field(False, description="是否启用(默认false,需要配置API密钥后才能启用)")
|
||
|
||
|
||
class ProviderUpdate(BaseModel):
|
||
"""更新提供商请求"""
|
||
|
||
display_name: Optional[str] = Field(None, min_length=1, max_length=100)
|
||
description: Optional[str] = None
|
||
website: Optional[str] = Field(None, max_length=500)
|
||
api_format: Optional[str] = None
|
||
base_url: Optional[str] = None
|
||
headers: Optional[dict] = None
|
||
timeout: Optional[int] = Field(None, ge=1, le=600)
|
||
max_retries: Optional[int] = Field(None, ge=0, le=10)
|
||
priority: Optional[int] = None
|
||
weight: Optional[float] = Field(None, gt=0)
|
||
rate_limit: Optional[int] = None
|
||
concurrent_limit: Optional[int] = None
|
||
config: Optional[dict] = None
|
||
is_active: Optional[bool] = None
|
||
|
||
|
||
class ProviderResponse(BaseModel):
|
||
"""提供商响应"""
|
||
|
||
id: str
|
||
name: str
|
||
display_name: str
|
||
description: Optional[str]
|
||
website: Optional[str]
|
||
api_format: str
|
||
base_url: str
|
||
headers: Optional[dict]
|
||
timeout: int
|
||
max_retries: int
|
||
priority: int
|
||
weight: float
|
||
rate_limit: Optional[int]
|
||
concurrent_limit: Optional[int]
|
||
config: Optional[dict]
|
||
is_active: bool
|
||
created_at: datetime
|
||
updated_at: datetime
|
||
models_count: int = 0
|
||
active_models_count: int = 0
|
||
api_keys_count: int = 0
|
||
|
||
model_config = ConfigDict(from_attributes=True)
|
||
|
||
|
||
# ========== 模型管理 ==========
|
||
class ModelCreate(BaseModel):
|
||
"""创建模型请求 - 价格和能力字段可选,为空时使用 GlobalModel 默认值"""
|
||
|
||
provider_model_name: str = Field(
|
||
..., min_length=1, max_length=200, description="Provider 侧的主模型名称"
|
||
)
|
||
provider_model_mappings: Optional[List[dict]] = Field(
|
||
None,
|
||
description="模型名称映射列表,格式: [{'name': 'alias1', 'priority': 1}, ...]",
|
||
)
|
||
global_model_id: str = Field(..., description="关联的 GlobalModel ID(必填)")
|
||
# 按次计费配置 - 可选,为空时使用 GlobalModel 默认值
|
||
price_per_request: Optional[float] = Field(
|
||
None, ge=0, description="每次请求固定费用,为空使用默认值"
|
||
)
|
||
# 阶梯计费配置 - 可选,为空时使用 GlobalModel 默认值
|
||
tiered_pricing: Optional[dict] = Field(
|
||
None, description="阶梯计费配置,为空使用 GlobalModel 默认值"
|
||
)
|
||
# 能力配置 - 可选,为空时使用 GlobalModel 默认值
|
||
supports_vision: Optional[bool] = Field(None, description="是否支持图像输入,为空使用默认值")
|
||
supports_function_calling: Optional[bool] = Field(
|
||
None, description="是否支持函数调用,为空使用默认值"
|
||
)
|
||
supports_streaming: Optional[bool] = Field(None, description="是否支持流式输出,为空使用默认值")
|
||
supports_extended_thinking: Optional[bool] = Field(
|
||
None, description="是否支持扩展思考,为空使用默认值"
|
||
)
|
||
is_active: bool = Field(True, description="是否启用")
|
||
config: Optional[dict] = Field(None, description="额外配置")
|
||
|
||
|
||
class ModelUpdate(BaseModel):
|
||
"""更新模型请求"""
|
||
|
||
provider_model_name: Optional[str] = Field(None, min_length=1, max_length=200)
|
||
provider_model_mappings: Optional[List[dict]] = Field(
|
||
None,
|
||
description="模型名称映射列表,格式: [{'name': 'alias1', 'priority': 1}, ...]",
|
||
)
|
||
global_model_id: Optional[str] = None
|
||
# 按次计费配置
|
||
price_per_request: Optional[float] = Field(None, ge=0, description="每次请求固定费用")
|
||
# 阶梯计费配置
|
||
tiered_pricing: Optional[dict] = Field(None, description="阶梯计费配置")
|
||
supports_vision: Optional[bool] = None
|
||
supports_function_calling: Optional[bool] = None
|
||
supports_streaming: Optional[bool] = None
|
||
supports_extended_thinking: Optional[bool] = None
|
||
is_active: Optional[bool] = None
|
||
is_available: Optional[bool] = None
|
||
config: Optional[dict] = None
|
||
|
||
|
||
class ModelResponse(BaseModel):
|
||
"""模型响应 - 包含 Model 配置和关联的 GlobalModel 信息
|
||
|
||
注意:价格和能力字段返回的是有效值(优先使用 Model 配置,否则使用 GlobalModel 默认值)
|
||
"""
|
||
|
||
id: str
|
||
provider_id: str
|
||
global_model_id: Optional[str]
|
||
provider_model_name: str
|
||
provider_model_mappings: Optional[List[dict]] = None
|
||
|
||
# 按次计费配置
|
||
price_per_request: Optional[float] = None
|
||
# 阶梯计费配置
|
||
tiered_pricing: Optional[dict] = None
|
||
|
||
# Provider 能力配置 - 可选,为空表示使用 GlobalModel 默认值
|
||
supports_vision: Optional[bool]
|
||
supports_function_calling: Optional[bool]
|
||
supports_streaming: Optional[bool]
|
||
supports_extended_thinking: Optional[bool]
|
||
supports_image_generation: Optional[bool]
|
||
|
||
# 有效值(合并 Model 配置和 GlobalModel 默认值后的结果)
|
||
effective_tiered_pricing: Optional[dict] = None
|
||
effective_input_price: Optional[float] = None
|
||
effective_output_price: Optional[float] = None
|
||
effective_price_per_request: Optional[float] = None
|
||
effective_supports_vision: Optional[bool] = None
|
||
effective_supports_function_calling: Optional[bool] = None
|
||
effective_supports_streaming: Optional[bool] = None
|
||
effective_supports_extended_thinking: Optional[bool] = None
|
||
effective_supports_image_generation: Optional[bool] = None
|
||
|
||
# 状态
|
||
is_active: bool
|
||
is_available: bool
|
||
|
||
# 时间戳
|
||
created_at: datetime
|
||
updated_at: datetime
|
||
|
||
# 关联的 GlobalModel 信息(如果有)
|
||
global_model_name: Optional[str] = None
|
||
global_model_display_name: Optional[str] = None
|
||
|
||
model_config = ConfigDict(from_attributes=True)
|
||
|
||
|
||
class ModelDetailResponse(BaseModel):
|
||
"""模型详细响应 - 包含所有字段(用于需要完整信息的场景)"""
|
||
|
||
id: str
|
||
provider_id: str
|
||
name: str
|
||
display_name: str
|
||
description: Optional[str]
|
||
icon_url: Optional[str]
|
||
tags: Optional[List[str]]
|
||
input_price_per_1m: float
|
||
output_price_per_1m: float
|
||
cache_creation_price_per_1m: Optional[float]
|
||
cache_read_price_per_1m: Optional[float]
|
||
supports_vision: bool
|
||
supports_function_calling: bool
|
||
supports_streaming: bool
|
||
is_active: bool
|
||
is_available: bool
|
||
config: Optional[dict]
|
||
created_at: datetime
|
||
updated_at: datetime
|
||
|
||
model_config = ConfigDict(from_attributes=True)
|
||
|
||
|
||
# ========== 系统设置 ==========
|
||
class SystemSettingsRequest(BaseModel):
|
||
"""系统设置请求"""
|
||
|
||
default_provider: Optional[str] = None
|
||
default_model: Optional[str] = None
|
||
enable_usage_tracking: Optional[bool] = None
|
||
|
||
|
||
class SystemSettingsResponse(BaseModel):
|
||
"""系统设置响应"""
|
||
|
||
default_provider: Optional[str]
|
||
default_model: Optional[str]
|
||
enable_usage_tracking: bool
|
||
|
||
|
||
# ========== 使用统计 ==========
|
||
class UsageStatsResponse(BaseModel):
|
||
"""使用统计响应"""
|
||
|
||
total_requests: int
|
||
total_tokens: int
|
||
total_cost_usd: float
|
||
daily_requests: int
|
||
daily_tokens: int
|
||
daily_cost_usd: float
|
||
model_usage: Dict[str, Dict[str, Any]]
|
||
provider_usage: Dict[str, Dict[str, Any]]
|
||
|
||
|
||
# ========== 公开API响应模型 ==========
|
||
class PublicProviderResponse(BaseModel):
|
||
"""公开的提供商信息响应"""
|
||
|
||
id: str
|
||
name: str
|
||
display_name: str
|
||
description: Optional[str]
|
||
website: Optional[str]
|
||
is_active: bool
|
||
provider_priority: int # 提供商优先级(数字越小越优先)
|
||
# 统计信息
|
||
models_count: int
|
||
active_models_count: int
|
||
endpoints_count: int # 端点总数
|
||
active_endpoints_count: int # 活跃端点数
|
||
|
||
|
||
class PublicModelResponse(BaseModel):
|
||
"""公开的模型信息响应"""
|
||
|
||
id: str
|
||
provider_id: str
|
||
provider_name: str
|
||
provider_display_name: str
|
||
name: str
|
||
display_name: str
|
||
description: Optional[str] = None
|
||
tags: Optional[List[str]] = None
|
||
icon_url: Optional[str] = None
|
||
# 价格信息
|
||
input_price_per_1m: Optional[float] = None
|
||
output_price_per_1m: Optional[float] = None
|
||
cache_creation_price_per_1m: Optional[float] = None
|
||
cache_read_price_per_1m: Optional[float] = None
|
||
# 功能支持
|
||
supports_vision: Optional[bool] = None
|
||
supports_function_calling: Optional[bool] = None
|
||
supports_streaming: Optional[bool] = None
|
||
is_active: bool = True
|
||
|
||
|
||
class ProviderStatsResponse(BaseModel):
|
||
"""提供商统计信息响应"""
|
||
|
||
total_providers: int
|
||
active_providers: int
|
||
total_models: int
|
||
active_models: int
|
||
supported_formats: List[str]
|
||
|
||
|
||
class PublicGlobalModelResponse(BaseModel):
|
||
"""公开的 GlobalModel 信息响应(用户可见)"""
|
||
|
||
id: str
|
||
name: str
|
||
display_name: Optional[str] = None
|
||
is_active: bool = True
|
||
# 按次计费配置
|
||
default_price_per_request: Optional[float] = None
|
||
# 阶梯计费配置
|
||
default_tiered_pricing: Optional[dict] = None
|
||
# Key 能力配置
|
||
supported_capabilities: Optional[List[str]] = None
|
||
# 模型配置(JSON)
|
||
config: Optional[dict] = None
|
||
|
||
|
||
class PublicGlobalModelListResponse(BaseModel):
|
||
"""公开的 GlobalModel 列表响应"""
|
||
|
||
models: List[PublicGlobalModelResponse]
|
||
total: int
|
||
|
||
|
||
# ========== 个人中心相关模型 ==========
|
||
class UpdateProfileRequest(BaseModel):
|
||
"""更新个人信息请求"""
|
||
|
||
email: Optional[str] = None
|
||
username: Optional[str] = None
|
||
|
||
|
||
class UpdatePreferencesRequest(BaseModel):
|
||
"""更新偏好设置请求"""
|
||
|
||
avatar_url: Optional[str] = None
|
||
bio: Optional[str] = None
|
||
default_provider_id: Optional[int] = None
|
||
theme: Optional[str] = None
|
||
language: Optional[str] = None
|
||
timezone: Optional[str] = None
|
||
email_notifications: Optional[bool] = None
|
||
usage_alerts: Optional[bool] = None
|
||
announcement_notifications: Optional[bool] = None
|
||
|
||
|
||
class ChangePasswordRequest(BaseModel):
|
||
"""修改密码请求"""
|
||
|
||
old_password: str
|
||
new_password: str
|
||
|
||
|
||
class CreateMyApiKeyRequest(BaseModel):
|
||
"""创建我的API密钥请求"""
|
||
|
||
name: str
|
||
|
||
|
||
class ProviderConfig(BaseModel):
|
||
"""提供商配置"""
|
||
|
||
provider_id: str = Field(..., description="提供商ID")
|
||
priority: int = Field(100, description="优先级(越高越优先)")
|
||
weight: float = Field(1.0, description="负载均衡权重")
|
||
enabled: bool = Field(True, description="是否启用")
|
||
|
||
|
||
class UpdateApiKeyProvidersRequest(BaseModel):
|
||
"""更新API密钥可用提供商请求"""
|
||
|
||
allowed_providers: Optional[List[ProviderConfig]] = None # 提供商配置列表
|
||
|
||
|
||
# ========== 公告相关模型 ==========
|
||
class CreateAnnouncementRequest(BaseModel):
|
||
"""创建公告请求"""
|
||
|
||
title: str
|
||
content: str # 支持Markdown
|
||
type: str = "info" # info, warning, maintenance, important
|
||
priority: int = 0
|
||
is_pinned: bool = False
|
||
start_time: Optional[datetime] = None
|
||
end_time: Optional[datetime] = None
|
||
|
||
|
||
class UpdateAnnouncementRequest(BaseModel):
|
||
"""更新公告请求"""
|
||
|
||
title: Optional[str] = None
|
||
content: Optional[str] = None
|
||
type: Optional[str] = None
|
||
priority: Optional[int] = None
|
||
is_active: Optional[bool] = None
|
||
is_pinned: Optional[bool] = None
|
||
start_time: Optional[datetime] = None
|
||
end_time: Optional[datetime] = None
|