51521fc0dd
- 微信支付从 APIv3 降级为 APIv2(MD5/XML) - 积分系统:充值下单、微信回调、消费冻结/结算/退款 - SMS B2M 短信验证码服务 - 双 Token 认证(Access 30min + Refresh 30days) - SSE 单设备踢人 - 用户设备管理、积分账户模型 - Alembic 迁移脚本
67 lines
1.7 KiB
Python
67 lines
1.7 KiB
Python
"""
|
||
用户设备模型
|
||
============
|
||
|
||
单设备登录约束:一个用户同一时间只能在一个设备上登录。
|
||
user_id 字段带 UNIQUE 约束,强制 1:1 关系。
|
||
"""
|
||
|
||
from datetime import UTC, datetime
|
||
|
||
import uuid
|
||
from sqlalchemy.dialects.postgresql import UUID
|
||
from sqlalchemy import ForeignKey, String
|
||
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="最后活跃时间",
|
||
)
|