@@ -0,0 +1,60 @@
|
||||
"""通知提醒:幂等键 + 到期扫描的只读口子
|
||||
|
||||
Revision ID: 0017
|
||||
Revises: 0016
|
||||
Create Date: 2026-11-10
|
||||
|
||||
两件事:
|
||||
|
||||
1. **提醒的幂等键**。到期/逾期提醒由后台循环每几分钟扫一次,扫描本身有
|
||||
advisory lock 保证只有一个进程在跑,但进程重启、时钟抖动、人工重跑都可能
|
||||
让同一条提醒再写一次。`dedupe_key` + 部分唯一索引把"同一件事只提醒一次"
|
||||
交给数据库保证,调用方用 `ON CONFLICT DO NOTHING` 即可。普通通知的
|
||||
`dedupe_key` 为空,不参与唯一约束。
|
||||
|
||||
2. **到期扫描要跨租户列出待办**。提醒是平台级定时任务,扫的时候没有"当前工作区",
|
||||
`item` 的隔离策略会让它一行都看不到。这里照 0016 的做法加一条只在
|
||||
`app.reminder_list = 'on'` 时生效的 **SELECT-only** 策略:扫描器能列出
|
||||
全平台的截止时间,但写通知仍然必须逐个工作区绑定租户,改不了别人的数据。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
from app.roles import APP_ROLE
|
||||
|
||||
revision: str = "0017"
|
||||
down_revision: str = "0016"
|
||||
branch_labels: Sequence[str] | None = None
|
||||
depends_on: Sequence[str] | None = None
|
||||
|
||||
CREATE_REMINDER_POLICY = """
|
||||
CREATE POLICY item_reminder_list ON item
|
||||
FOR SELECT
|
||||
USING (current_setting('app.reminder_list', true) = 'on')
|
||||
"""
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column("notification", sa.Column("dedupe_key", sa.String(120), nullable=True))
|
||||
op.create_index(
|
||||
"uq_notification_dedupe",
|
||||
"notification",
|
||||
["workspace_id", "user_id", "dedupe_key"],
|
||||
unique=True,
|
||||
postgresql_where=sa.text("dedupe_key IS NOT NULL"),
|
||||
)
|
||||
|
||||
op.execute("DROP POLICY IF EXISTS item_reminder_list ON item")
|
||||
op.execute(CREATE_REMINDER_POLICY)
|
||||
op.execute(f"GRANT SELECT ON item TO {APP_ROLE}")
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.execute("DROP POLICY IF EXISTS item_reminder_list ON item")
|
||||
op.drop_index("uq_notification_dedupe", table_name="notification")
|
||||
op.drop_column("notification", "dedupe_key")
|
||||
Reference in New Issue
Block a user