0036_notification.py 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. """站内通知表 sys_notification(契约 1.34)。
  2. Revision ID: 0036_notification
  3. Revises: 0035_drop_audit_action_check
  4. Create Date: 2026-09-15
  5. 变更要点:
  6. 1. 新建 sys_notification:用户站内通知(审批待办等),追加型表,
  7. 不含业务表 mixin 的软删/乐观锁列;
  8. 2. 双引擎幂等:先探测表存在再建(MySQL 与达梦升级互不冲突);
  9. 3. 降级保护:存在通知记录时拒绝有损降级。
  10. """
  11. from __future__ import annotations
  12. from collections.abc import Sequence
  13. import sqlalchemy as sa
  14. from alembic import op
  15. from sqlalchemy.dialects import mysql
  16. revision: str = "0036_notification"
  17. down_revision: str | None = "0035_drop_audit_action_check"
  18. branch_labels: str | Sequence[str] | None = None
  19. depends_on: str | Sequence[str] | None = None
  20. def _table_exists(table: str) -> bool:
  21. # 达梦 inspector 返回大写表名,统一小写比较
  22. inspector = sa.inspect(op.get_bind())
  23. return table.lower() in {name.lower() for name in inspector.get_table_names()}
  24. def upgrade() -> None:
  25. if _table_exists("sys_notification"):
  26. return
  27. op.create_table(
  28. "sys_notification",
  29. sa.Column("id", sa.BigInteger(), autoincrement=True, nullable=False),
  30. sa.Column("user_id", sa.BigInteger(), nullable=False),
  31. sa.Column("title", sa.String(length=200), nullable=False),
  32. sa.Column("content", sa.String(length=1000), nullable=True),
  33. sa.Column("notify_type", sa.String(length=32), nullable=False),
  34. sa.Column("action", sa.String(length=16), nullable=False),
  35. sa.Column("status", sa.String(length=16), nullable=False),
  36. sa.Column(
  37. "created_at",
  38. mysql.DATETIME(fsp=3),
  39. server_default=sa.text("CURRENT_TIMESTAMP(3)"),
  40. nullable=False,
  41. ),
  42. sa.Column("handled_at", mysql.DATETIME(fsp=3), nullable=True),
  43. sa.PrimaryKeyConstraint("id"),
  44. sa.CheckConstraint(
  45. "notify_type IN ('APPROVAL')",
  46. name="notification_notify_type",
  47. ),
  48. sa.CheckConstraint(
  49. "action IN ('NONE', 'PENDING', 'AGREE', 'REJECT')",
  50. name="notification_action",
  51. ),
  52. sa.CheckConstraint(
  53. "status IN ('UNREAD', 'READ')",
  54. name="notification_status",
  55. ),
  56. )
  57. op.create_index(
  58. "ix_sys_notification_user_id",
  59. "sys_notification",
  60. ["user_id", "id"],
  61. )
  62. def downgrade() -> None:
  63. if not _table_exists("sys_notification"):
  64. return
  65. rows = op.get_bind().scalar(sa.text("SELECT COUNT(*) FROM sys_notification"))
  66. if rows:
  67. raise RuntimeError(
  68. "0036已存在通知记录,拒绝有损降级;请先备份并按数据治理流程处理"
  69. )
  70. op.drop_index("ix_sys_notification_user_id", table_name="sys_notification")
  71. op.drop_table("sys_notification")