b597d715c8
后端: - 修复 get_current_user 未校验 is_active,被封禁用户仍可用旧 Token - auth.py 捕获 ValueError 转 HTTPException(验证码错误、账号被封、Token 无效等不再返回 500) - 修正 SMS 每日上限注释(3次 → 10次) - 修复迁移脚本外键引用错误:users.id → mjk_users.id - 新建积分系统 4 张表的迁移(mjk_user_points/batches/transactions/recharge_orders) - pyproject.toml 补充 alembic + psycopg2-binary 依赖 - ruff 格式修复(import 排序等) 前端: - 修复 doRefreshToken 成功后不持久化新 Token 的严重 bug - 修复应用重启后 SSE 不自动重连(收不到踢人通知) - 修复 App.tsx handleLogout 未 await - client.ts 统一从 utils/env 导入 isTauri,默认 base URL 兜底 localhost:8000 - 清理 ~20 个未使用的 hooks/utils/api 模块/组件导出 - 修复所有 ESLint 警告(206 → 0)和 TSC 错误 - 测试通过(5/5) 其他: - 更新 requirements.lock 和 uv.lock
67 lines
1.7 KiB
Python
67 lines
1.7 KiB
Python
"""
|
||
用户设备模型
|
||
============
|
||
|
||
单设备登录约束:一个用户同一时间只能在一个设备上登录。
|
||
user_id 字段带 UNIQUE 约束,强制 1:1 关系。
|
||
"""
|
||
|
||
import uuid
|
||
from datetime import UTC, datetime
|
||
|
||
from sqlalchemy import ForeignKey, String
|
||
from sqlalchemy.dialects.postgresql import UUID
|
||
from sqlalchemy.orm import Mapped, mapped_column
|
||
|
||
from app.models.base import BaseModelBigInt
|
||
|
||
|
||
class UserDevice(BaseModelBigInt):
|
||
"""用户设备表(单设备登录)"""
|
||
|
||
__tablename__ = "mjk_user_devices"
|
||
|
||
user_id: Mapped[uuid.UUID] = mapped_column(
|
||
UUID(as_uuid=True),
|
||
ForeignKey("mjk_users.id", ondelete="CASCADE"),
|
||
nullable=False,
|
||
unique=True,
|
||
comment="用户 ID(唯一约束,强制单设备登录)",
|
||
)
|
||
|
||
device_id: Mapped[str] = mapped_column(
|
||
String(64),
|
||
nullable=False,
|
||
comment="设备唯一标识(前端生成)",
|
||
)
|
||
|
||
device_name: Mapped[str | None] = mapped_column(
|
||
String(128),
|
||
nullable=True,
|
||
comment="设备名称(如 'MacBook Pro')",
|
||
)
|
||
|
||
os_info: Mapped[str | None] = mapped_column(
|
||
String(128),
|
||
nullable=True,
|
||
comment="操作系统信息",
|
||
)
|
||
|
||
app_version: Mapped[str | None] = mapped_column(
|
||
String(32),
|
||
nullable=True,
|
||
comment="应用版本号",
|
||
)
|
||
|
||
refresh_token_hash: Mapped[str | None] = mapped_column(
|
||
String(64),
|
||
nullable=True,
|
||
comment="Refresh Token SHA256 哈希(用于校验和撤销)",
|
||
)
|
||
|
||
last_active_at: Mapped[datetime] = mapped_column(
|
||
default=lambda: datetime.now(UTC),
|
||
nullable=False,
|
||
comment="最后活跃时间",
|
||
)
|