提供基本前后端骨架
This commit is contained in:
285
app/schemas/balance.py
Normal file
285
app/schemas/balance.py
Normal file
@@ -0,0 +1,285 @@
|
||||
"""
|
||||
余额相关 Schema
|
||||
|
||||
定义余额数据的验证和序列化规则。
|
||||
|
||||
设计说明:
|
||||
- 内部存储使用整数单位(units),外部显示使用小数(display)
|
||||
- 1.00 显示余额 = 1000 单位额度
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Annotated
|
||||
|
||||
from pydantic import Field, field_validator, computed_field
|
||||
|
||||
from app.schemas.base import BaseSchema, PaginatedResponse
|
||||
from app.models.balance import TransactionType, TransactionStatus
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 余额单位转换工具
|
||||
# ============================================================
|
||||
|
||||
UNITS_PER_DISPLAY = 1000 # 1.00 显示余额 = 1000 单位额度
|
||||
|
||||
|
||||
def display_to_units(display_amount: float) -> int:
|
||||
"""将显示金额转换为单位额度"""
|
||||
return int(round(display_amount * UNITS_PER_DISPLAY))
|
||||
|
||||
|
||||
def units_to_display(units: int) -> float:
|
||||
"""将单位额度转换为显示金额"""
|
||||
return units / UNITS_PER_DISPLAY
|
||||
|
||||
|
||||
def format_display(units: int) -> str:
|
||||
"""格式化显示金额(2 位小数)"""
|
||||
return f"{units / UNITS_PER_DISPLAY:.2f}"
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 余额账户 Schema
|
||||
# ============================================================
|
||||
|
||||
class BalanceResponse(BaseSchema):
|
||||
"""余额信息响应"""
|
||||
|
||||
user_id: str
|
||||
balance_units: Annotated[
|
||||
int,
|
||||
Field(description="当前余额(单位额度)"),
|
||||
]
|
||||
frozen_units: Annotated[
|
||||
int,
|
||||
Field(description="冻结余额(单位额度)"),
|
||||
]
|
||||
total_recharged_units: Annotated[
|
||||
int,
|
||||
Field(description="累计充值(单位额度)"),
|
||||
]
|
||||
total_consumed_units: Annotated[
|
||||
int,
|
||||
Field(description="累计消费(单位额度)"),
|
||||
]
|
||||
|
||||
@computed_field
|
||||
@property
|
||||
def balance(self) -> str:
|
||||
"""显示余额(2 位小数)"""
|
||||
return format_display(self.balance_units)
|
||||
|
||||
@computed_field
|
||||
@property
|
||||
def available_balance(self) -> str:
|
||||
"""显示可用余额(2 位小数)"""
|
||||
return format_display(self.balance_units - self.frozen_units)
|
||||
|
||||
@computed_field
|
||||
@property
|
||||
def frozen_balance(self) -> str:
|
||||
"""显示冻结余额(2 位小数)"""
|
||||
return format_display(self.frozen_units)
|
||||
|
||||
@computed_field
|
||||
@property
|
||||
def total_recharged(self) -> str:
|
||||
"""显示累计充值(2 位小数)"""
|
||||
return format_display(self.total_recharged_units)
|
||||
|
||||
@computed_field
|
||||
@property
|
||||
def total_consumed(self) -> str:
|
||||
"""显示累计消费(2 位小数)"""
|
||||
return format_display(self.total_consumed_units)
|
||||
|
||||
|
||||
class BalanceSummaryResponse(BaseSchema):
|
||||
"""余额简要信息响应(用于嵌入用户信息)"""
|
||||
|
||||
balance: str = Field(description="当前余额")
|
||||
available_balance: str = Field(description="可用余额")
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 交易记录 Schema
|
||||
# ============================================================
|
||||
|
||||
class TransactionResponse(BaseSchema):
|
||||
"""交易记录响应"""
|
||||
|
||||
id: str
|
||||
transaction_type: TransactionType
|
||||
status: TransactionStatus
|
||||
amount_units: Annotated[
|
||||
int,
|
||||
Field(description="交易金额(单位额度,正数收入,负数支出)"),
|
||||
]
|
||||
balance_before_units: Annotated[
|
||||
int,
|
||||
Field(description="交易前余额(单位额度)"),
|
||||
]
|
||||
balance_after_units: Annotated[
|
||||
int,
|
||||
Field(description="交易后余额(单位额度)"),
|
||||
]
|
||||
reference_type: str | None
|
||||
reference_id: str | None
|
||||
description: str | None
|
||||
created_at: datetime
|
||||
|
||||
@computed_field
|
||||
@property
|
||||
def amount(self) -> str:
|
||||
"""显示交易金额(带符号,2 位小数)"""
|
||||
return f"{self.amount_units / UNITS_PER_DISPLAY:+.2f}"
|
||||
|
||||
@computed_field
|
||||
@property
|
||||
def balance_before(self) -> str:
|
||||
"""显示交易前余额(2 位小数)"""
|
||||
return format_display(self.balance_before_units)
|
||||
|
||||
@computed_field
|
||||
@property
|
||||
def balance_after(self) -> str:
|
||||
"""显示交易后余额(2 位小数)"""
|
||||
return format_display(self.balance_after_units)
|
||||
|
||||
|
||||
class TransactionListResponse(PaginatedResponse[TransactionResponse]):
|
||||
"""交易记录列表响应"""
|
||||
pass
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 扣款请求 Schema
|
||||
# ============================================================
|
||||
|
||||
class DeductionRequest(BaseSchema):
|
||||
"""扣款请求"""
|
||||
|
||||
amount: Annotated[
|
||||
float,
|
||||
Field(
|
||||
gt=0,
|
||||
description="扣款金额(显示金额,如 1.00)",
|
||||
examples=[1.00, 0.50],
|
||||
),
|
||||
]
|
||||
reference_type: Annotated[
|
||||
str | None,
|
||||
Field(
|
||||
default=None,
|
||||
max_length=64,
|
||||
description="关联业务类型",
|
||||
examples=["api_call", "service_fee"],
|
||||
),
|
||||
]
|
||||
reference_id: Annotated[
|
||||
str | None,
|
||||
Field(
|
||||
default=None,
|
||||
max_length=64,
|
||||
description="关联业务 ID",
|
||||
),
|
||||
]
|
||||
description: Annotated[
|
||||
str | None,
|
||||
Field(
|
||||
default=None,
|
||||
max_length=255,
|
||||
description="交易描述",
|
||||
),
|
||||
]
|
||||
idempotency_key: Annotated[
|
||||
str | None,
|
||||
Field(
|
||||
default=None,
|
||||
max_length=64,
|
||||
description="幂等键(防止重复扣款)",
|
||||
),
|
||||
]
|
||||
|
||||
@field_validator("amount")
|
||||
@classmethod
|
||||
def validate_amount(cls, v: float) -> float:
|
||||
"""验证金额精度"""
|
||||
# 最小精度 0.001(即 1 单位额度)
|
||||
if round(v, 3) != v:
|
||||
raise ValueError("金额精度不能超过 3 位小数")
|
||||
return v
|
||||
|
||||
@property
|
||||
def amount_units(self) -> int:
|
||||
"""转换为单位额度"""
|
||||
return display_to_units(self.amount)
|
||||
|
||||
|
||||
class DeductionResponse(BaseSchema):
|
||||
"""扣款响应"""
|
||||
|
||||
transaction_id: str
|
||||
amount: str = Field(description="扣款金额")
|
||||
balance_before: str = Field(description="扣款前余额")
|
||||
balance_after: str = Field(description="扣款后余额")
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 管理员操作 Schema
|
||||
# ============================================================
|
||||
|
||||
class AdminAdjustmentRequest(BaseSchema):
|
||||
"""管理员余额调整请求"""
|
||||
|
||||
user_id: Annotated[
|
||||
str,
|
||||
Field(description="目标用户 ID"),
|
||||
]
|
||||
amount: Annotated[
|
||||
float,
|
||||
Field(
|
||||
description="调整金额(正数增加,负数减少)",
|
||||
examples=[10.00, -5.00],
|
||||
),
|
||||
]
|
||||
reason: Annotated[
|
||||
str,
|
||||
Field(
|
||||
min_length=1,
|
||||
max_length=255,
|
||||
description="调整原因",
|
||||
),
|
||||
]
|
||||
|
||||
@field_validator("amount")
|
||||
@classmethod
|
||||
def validate_amount(cls, v: float) -> float:
|
||||
"""验证金额精度"""
|
||||
if round(v, 3) != v:
|
||||
raise ValueError("金额精度不能超过 3 位小数")
|
||||
if v == 0:
|
||||
raise ValueError("调整金额不能为 0")
|
||||
return v
|
||||
|
||||
@property
|
||||
def amount_units(self) -> int:
|
||||
"""转换为单位额度"""
|
||||
return display_to_units(self.amount)
|
||||
|
||||
|
||||
class AdminBalanceResponse(BaseSchema):
|
||||
"""管理员查看的余额信息(包含更多细节)"""
|
||||
|
||||
user_id: str
|
||||
username: str
|
||||
balance: str
|
||||
available_balance: str
|
||||
frozen_balance: str
|
||||
total_recharged: str
|
||||
total_consumed: str
|
||||
version: int
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
Reference in New Issue
Block a user