提供基本前后端骨架

This commit is contained in:
hisatri
2026-01-06 23:49:23 +08:00
parent 84d4ccc226
commit 06f8176e23
89 changed files with 19293 additions and 2 deletions

285
app/schemas/balance.py Normal file
View 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