142 lines
3.3 KiB
Python
142 lines
3.3 KiB
Python
"""
|
||
用户模型
|
||
|
||
定义用户数据表结构。
|
||
"""
|
||
|
||
from datetime import datetime, timezone
|
||
from typing import TYPE_CHECKING
|
||
from uuid import uuid4
|
||
|
||
from sqlalchemy import Boolean, DateTime, String, Text, func
|
||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||
|
||
from app.database import Base
|
||
|
||
if TYPE_CHECKING:
|
||
from app.models.balance import UserBalance
|
||
|
||
|
||
def generate_uuid() -> str:
|
||
"""生成 UUID 字符串"""
|
||
return str(uuid4())
|
||
|
||
|
||
def utc_now() -> datetime:
|
||
"""获取当前 UTC 时间"""
|
||
return datetime.now(timezone.utc)
|
||
|
||
|
||
class User(Base):
|
||
"""用户模型"""
|
||
|
||
__tablename__ = "users"
|
||
|
||
# 主键:使用 UUID 字符串
|
||
id: Mapped[str] = mapped_column(
|
||
String(36),
|
||
primary_key=True,
|
||
default=generate_uuid,
|
||
comment="用户唯一标识",
|
||
)
|
||
|
||
# 账户信息
|
||
username: Mapped[str] = mapped_column(
|
||
String(32),
|
||
unique=True,
|
||
index=True,
|
||
nullable=False,
|
||
comment="用户名",
|
||
)
|
||
email: Mapped[str | None] = mapped_column(
|
||
String(255),
|
||
unique=True,
|
||
index=True,
|
||
nullable=True,
|
||
comment="邮箱地址",
|
||
)
|
||
hashed_password: Mapped[str | None] = mapped_column(
|
||
String(255),
|
||
nullable=True, # OAuth2 用户可能没有密码
|
||
comment="密码哈希",
|
||
)
|
||
|
||
# OAuth2 关联信息
|
||
oauth_provider: Mapped[str | None] = mapped_column(
|
||
String(32),
|
||
nullable=True,
|
||
index=True,
|
||
comment="OAuth2 提供商(如 linuxdo)",
|
||
)
|
||
oauth_user_id: Mapped[str | None] = mapped_column(
|
||
String(128),
|
||
nullable=True,
|
||
index=True,
|
||
comment="OAuth2 用户 ID",
|
||
)
|
||
|
||
# 用户状态
|
||
is_active: Mapped[bool] = mapped_column(
|
||
Boolean,
|
||
default=True,
|
||
nullable=False,
|
||
comment="是否激活",
|
||
)
|
||
is_superuser: Mapped[bool] = mapped_column(
|
||
Boolean,
|
||
default=False,
|
||
nullable=False,
|
||
comment="是否为超级管理员",
|
||
)
|
||
|
||
# 个人信息
|
||
nickname: Mapped[str | None] = mapped_column(
|
||
String(64),
|
||
nullable=True,
|
||
comment="昵称",
|
||
)
|
||
avatar_url: Mapped[str | None] = mapped_column(
|
||
String(512),
|
||
nullable=True,
|
||
comment="头像 URL",
|
||
)
|
||
bio: Mapped[str | None] = mapped_column(
|
||
Text,
|
||
nullable=True,
|
||
comment="个人简介",
|
||
)
|
||
|
||
# 时间戳
|
||
created_at: Mapped[datetime] = mapped_column(
|
||
DateTime(timezone=True),
|
||
default=utc_now,
|
||
server_default=func.now(),
|
||
nullable=False,
|
||
comment="创建时间",
|
||
)
|
||
updated_at: Mapped[datetime] = mapped_column(
|
||
DateTime(timezone=True),
|
||
default=utc_now,
|
||
onupdate=utc_now,
|
||
server_default=func.now(),
|
||
nullable=False,
|
||
comment="更新时间",
|
||
)
|
||
last_login_at: Mapped[datetime | None] = mapped_column(
|
||
DateTime(timezone=True),
|
||
nullable=True,
|
||
comment="最后登录时间",
|
||
)
|
||
|
||
# 关系
|
||
balance_account: Mapped["UserBalance | None"] = relationship(
|
||
"UserBalance",
|
||
back_populates="user",
|
||
uselist=False,
|
||
lazy="selectin",
|
||
)
|
||
|
||
def __repr__(self) -> str:
|
||
return f"<User(id={self.id!r}, username={self.username!r})>"
|
||
|