user.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. """系统用户模型。"""
  2. from __future__ import annotations
  3. from datetime import datetime
  4. from sqlalchemy import (
  5. BigInteger,
  6. CheckConstraint,
  7. Computed,
  8. DateTime,
  9. ForeignKey,
  10. Index,
  11. Integer,
  12. String,
  13. UniqueConstraint,
  14. text,
  15. )
  16. from sqlalchemy.dialects.mysql import DATETIME
  17. from sqlalchemy.orm import Mapped, mapped_column
  18. from dms.database.base import BusinessTableMixin
  19. from dms.extensions import db
  20. class User(BusinessTableMixin, db.Model):
  21. __tablename__ = "sys_user"
  22. __table_args__ = (
  23. UniqueConstraint(
  24. "external_id",
  25. "active_marker",
  26. name="uq_sys_user_external_id_active",
  27. ),
  28. Index(
  29. "ix_sys_user_external_active",
  30. "external_id",
  31. "is_deleted",
  32. ),
  33. CheckConstraint(
  34. "role_code IN ('USER', 'ADMIN')",
  35. name="user_role_code",
  36. ),
  37. CheckConstraint(
  38. "security_level IN "
  39. "('PUBLIC', 'INTERNAL', 'SECRET', 'CONFIDENTIAL', 'TOP_SECRET')",
  40. name="user_security_level",
  41. ),
  42. CheckConstraint(
  43. "status IN ('ENABLED', 'DISABLED')",
  44. name="user_status",
  45. ),
  46. CheckConstraint(
  47. "ui_theme IN ('blue', 'red', 'green')",
  48. name="user_ui_theme",
  49. ),
  50. Index(
  51. "ix_sys_user_organization_active_status",
  52. "organization_id",
  53. "is_deleted",
  54. "status",
  55. ),
  56. Index(
  57. "ix_sys_user_role_status_active",
  58. "role_code",
  59. "status",
  60. "is_deleted",
  61. ),
  62. Index(
  63. "ix_sys_user_role_active",
  64. "role_id",
  65. "is_deleted",
  66. ),
  67. )
  68. username: Mapped[str] = mapped_column(String(64), nullable=False, unique=True)
  69. password_hash: Mapped[str] = mapped_column(String(255), nullable=False)
  70. real_name: Mapped[str] = mapped_column(String(64), nullable=False)
  71. organization_id: Mapped[int | None] = mapped_column(
  72. BigInteger,
  73. ForeignKey("sys_organization.id", ondelete="RESTRICT"),
  74. nullable=True,
  75. )
  76. organization_name: Mapped[str | None] = mapped_column(String(128), nullable=True)
  77. role_code: Mapped[str] = mapped_column(
  78. String(32),
  79. nullable=False,
  80. server_default=text("'USER'"),
  81. )
  82. role_id: Mapped[int | None] = mapped_column(
  83. BigInteger,
  84. ForeignKey("sys_role.id", ondelete="RESTRICT"),
  85. nullable=True,
  86. )
  87. security_level: Mapped[str] = mapped_column(
  88. String(32),
  89. nullable=False,
  90. server_default=text("'INTERNAL'"),
  91. )
  92. status: Mapped[str] = mapped_column(
  93. String(20),
  94. nullable=False,
  95. server_default=text("'ENABLED'"),
  96. )
  97. auth_version: Mapped[int] = mapped_column(
  98. Integer,
  99. nullable=False,
  100. server_default=text("0"),
  101. )
  102. last_login_at: Mapped[datetime | None] = mapped_column(
  103. DATETIME(fsp=3),
  104. nullable=True,
  105. )
  106. last_login_ip: Mapped[str | None] = mapped_column(String(64), nullable=True)
  107. # 登录失败锁定:连续密码错误计数与锁定截止时间(UTC,NULL=未锁定)
  108. failed_login_count: Mapped[int] = mapped_column(
  109. Integer,
  110. nullable=False,
  111. server_default=text("0"),
  112. )
  113. locked_until: Mapped[datetime | None] = mapped_column(
  114. DateTime(),
  115. nullable=True,
  116. )
  117. external_id: Mapped[str | None] = mapped_column(String(128), nullable=True)
  118. # 界面主题偏好(账号级漫游,契约1.20):blue/red/green,存量与缺省为 blue
  119. ui_theme: Mapped[str] = mapped_column(
  120. String(12),
  121. nullable=False,
  122. server_default=text("'blue'"),
  123. )
  124. active_marker: Mapped[int | None] = mapped_column(
  125. Integer(),
  126. Computed("IF(is_deleted = 0, 1, NULL)", persisted=False),
  127. nullable=True,
  128. )