PayrollManagementSystem/backend/app/models/login_log.py
liuwenxin 273ebf4657 chore: 初始化项目结构与登录鉴权模块
后端 FastAPI 六层结构(api/services/repositories/models/schemas/core),
统一响应信封、全局异常、日志脱敏;6 张表经 Alembic 迁移建库,SQL 留存
db/migration;登录鉴权含图形验证码(Redis GETDEL 防重放)、JWT、登录限流、
登出黑名单(TTL=令牌剩余有效期)、登录日志。

前端 Vue3 + TS + Element Plus 骨架,含登录页、路由守卫、请求封装与 Vite 代理。

工程配置:uv 锁定 Python 3.12、ruff 检查与格式化、editorconfig、gitattributes。

测试 184 项(单元 155 / 集成 29)全绿,浏览器端到端 8 项通过。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-09 18:46:36 +08:00

54 lines
2.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""登录日志模型。"""
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="创建时间"
)