"""登录日志模型。""" from datetime import datetime from sqlalchemy import DateTime, ForeignKey, Index, String, text from sqlalchemy.orm import Mapped, mapped_column from app.models.base import BIGINT_UNSIGNED, TINYINT_TYPE, Base, mysql_table_args class LoginLog(Base): """登录日志。需求 2.5.1 要求记录用户登录行为。 不继承 TimestampMixin:日志是追加写、不可变的,没有"更新时间"语义, 也不使用 update_time 的 ON UPDATE 子句。 记录原则: - **只记登录尝试,绝不记录密码**(明文或哈希都不记) - 用户名原样记录而非仅存 user_id —— 账号不存在时也要能追溯 - 写入使用独立事务,失败不阻断登录(审计可丢,业务不可断) """ __tablename__ = "login_logs" __table_args__ = mysql_table_args( "登录日志表", Index("idx_login_logs_username", "username"), Index("idx_login_logs_create_time", "create_time"), ) id: Mapped[int] = mapped_column( BIGINT_UNSIGNED, primary_key=True, autoincrement=True, comment="主键ID" ) user_id: Mapped[int | None] = mapped_column( BIGINT_UNSIGNED, ForeignKey("users.id", ondelete="SET NULL"), comment="用户ID(账号不存在时为空)", ) username: Mapped[str] = mapped_column( String(64), nullable=False, comment="登录用户名(原样记录)" ) ip: Mapped[str | None] = mapped_column(String(45), comment="客户端IP(45位兼容IPv6)") user_agent: Mapped[str | None] = mapped_column( String(255), comment="客户端User-Agent(截断存储)" ) success: Mapped[int] = mapped_column( TINYINT_TYPE, nullable=False, server_default="0", comment="是否成功:1成功 0失败" ) fail_reason: Mapped[str | None] = mapped_column( String(64), comment="失败原因:验证码错误/用户名或密码错误/账号锁定/账号禁用" ) create_time: Mapped[datetime] = mapped_column( DateTime, nullable=False, server_default=text("CURRENT_TIMESTAMP"), comment="创建时间" )