""" 用户相关模型 """ from datetime import datetime from typing import List, Optional, TYPE_CHECKING from sqlalchemy import ( Boolean, Column, DateTime, Integer, String, Table, Text, ForeignKey, ) from sqlalchemy.orm import Mapped, mapped_column, relationship from app.models.base import BaseModel # 避免循环导入 if TYPE_CHECKING: from .role import Role # 用户角色关联表(多对多) user_roles_table = Table( "sys_user_role", BaseModel.metadata, Column("user_id", Integer, ForeignKey("sys_user.id"), primary_key=True), Column("role_id", Integer, ForeignKey("sys_role.id"), primary_key=True), ) class User(BaseModel): """ 用户表 """ __tablename__ = "sys_user" username: Mapped[str] = mapped_column( String(50), unique=True, index=True, nullable=False, comment="用户名" ) password: Mapped[str] = mapped_column(String(255), nullable=False, comment="密码哈希") nickname: Mapped[Optional[str]] = mapped_column( String(50), nullable=True, comment="昵称" ) email: Mapped[Optional[str]] = mapped_column( String(100), unique=True, index=True, nullable=True, comment="邮箱" ) phone: Mapped[Optional[str]] = mapped_column( String(20), nullable=True, comment="手机号" ) avatar: Mapped[Optional[str]] = mapped_column( String(255), nullable=True, comment="头像URL" ) status: Mapped[bool] = mapped_column( Boolean, default=True, comment="状态:0-禁用,1-正常" ) is_superuser: Mapped[bool] = mapped_column( Boolean, default=False, comment="是否超级用户" ) last_login_time: Mapped[Optional[datetime]] = mapped_column( DateTime(timezone=True), nullable=True, comment="最后登录时间" ) last_login_ip: Mapped[Optional[str]] = mapped_column( String(50), nullable=True, comment="最后登录IP" ) # 关联的企业实体ID(MCN机构ID或主播ID) entity_id: Mapped[Optional[str]] = mapped_column( String(50), nullable=True, comment="关联的企业实体ID(MCN机构或主播)" ) # 实体类型:mcn-机构,streamer-主播 entity_type: Mapped[Optional[str]] = mapped_column( String(20), nullable=True, comment="关联实体类型:mcn-机构,streamer-主播" ) # 关系 roles: Mapped[List["Role"]] = relationship( "Role", secondary=user_roles_table, back_populates="users" ) async def check_password(self, plain_password: str) -> bool: """ 检查密码 """ from app.utils.helpers import verify_password return verify_password(plain_password, self.password) async def set_password(self, plain_password: str) -> None: """ 设置密码 """ from app.utils.helpers import get_password_hash self.password = get_password_hash(plain_password) @classmethod async def get_by_username( cls, session, username: str ) -> Optional["User"]: """ 根据用户名获取用户 """ from sqlalchemy import select result = await session.execute(select(cls).where(cls.username == username)) return result.scalar_one_or_none() @classmethod async def get_by_email(cls, session, email: str) -> Optional["User"]: """ 根据邮箱获取用户 """ from sqlalchemy import select result = await session.execute(select(cls).where(cls.email == email)) return result.scalar_one_or_none() async def has_role(self, role_name: str) -> bool: """ 检查是否拥有特定角色 """ return any(role.role_code == role_name for role in self.roles) async def has_permission(self, permission: str) -> bool: """ 检查是否拥有特定权限 """ for role in self.roles: for perm in role.permissions: if perm.permission_code == permission: return True return False class Role(BaseModel): """ 角色表 """ __tablename__ = "sys_role" role_name: Mapped[str] = mapped_column( String(50), nullable=False, comment="角色名称" ) role_code: Mapped[str] = mapped_column( String(50), unique=True, nullable=False, comment="角色编码" ) description: Mapped[Optional[str]] = mapped_column( Text, nullable=True, comment="角色描述" ) status: Mapped[bool] = mapped_column( Boolean, default=True, comment="状态:0-禁用,1-正常" ) # 关系 users: Mapped[List[User]] = relationship( "User", secondary=user_roles_table, back_populates="roles" ) class Permission(BaseModel): """ 权限表 """ __tablename__ = "sys_permission" permission_name: Mapped[str] = mapped_column( String(50), nullable=False, comment="权限名称" ) permission_code: Mapped[str] = mapped_column( String(100), unique=True, nullable=False, comment="权限编码" ) resource_type: Mapped[str] = mapped_column( String(20), nullable=False, comment="资源类型:menu-菜单,button-按钮" ) resource_path: Mapped[Optional[str]] = mapped_column( String(100), nullable=True, comment="资源路径" ) description: Mapped[Optional[str]] = mapped_column( Text, nullable=True, comment="权限描述" ) status: Mapped[bool] = mapped_column( Boolean, default=True, comment="状态:0-禁用,1-正常" ) class UserLoginLog(BaseModel): """ 用户登录日志表 """ __tablename__ = "sys_user_login_log" user_id: Mapped[int] = mapped_column( Integer, nullable=False, comment="用户ID" ) username: Mapped[str] = mapped_column( String(50), nullable=False, comment="用户名" ) login_time: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=datetime.utcnow, comment="登录时间" ) login_ip: Mapped[Optional[str]] = mapped_column( String(50), nullable=True, comment="登录IP" ) user_agent: Mapped[Optional[str]] = mapped_column( Text, nullable=True, comment="用户代理" ) status: Mapped[bool] = mapped_column( Boolean, default=True, comment="登录状态:0-失败,1-成功" ) failure_reason: Mapped[Optional[str]] = mapped_column( String(255), nullable=True, comment="失败原因" ) class UserOperationLog(BaseModel): """ 用户操作日志表 """ __tablename__ = "sys_user_operation_log" user_id: Mapped[int] = mapped_column( Integer, nullable=False, comment="用户ID" ) username: Mapped[str] = mapped_column( String(50), nullable=False, comment="用户名" ) module: Mapped[str] = mapped_column( String(50), nullable=False, comment="模块名称" ) operation: Mapped[str] = mapped_column( String(100), nullable=False, comment="操作名称" ) method: Mapped[Optional[str]] = mapped_column( String(10), nullable=True, comment="请求方法" ) path: Mapped[Optional[str]] = mapped_column( String(200), nullable=True, comment="请求路径" ) params: Mapped[Optional[Text]] = mapped_column( Text, nullable=True, comment="请求参数" ) result: Mapped[Optional[Text]] = mapped_column( Text, nullable=True, comment="执行结果" ) operation_time: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=datetime.utcnow, comment="操作时间" ) ip_address: Mapped[Optional[str]] = mapped_column( String(50), nullable=True, comment="IP地址" ) user_agent: Mapped[Optional[str]] = mapped_column( Text, nullable=True, comment="用户代理" ) status: Mapped[bool] = mapped_column( Boolean, default=True, comment="操作状态:0-失败,1-成功" )