12 KiB
静态通知配置同步计划
更新时间:2026-04-10 状态:最终执行版
1. 目标
为通知系统增加一条独立的“静态配置 -> 数据库同步”链路,使服务端可以从仓库内的通知配置文件读取通知定义,并将其注册、更新或撤销到数据库。
本计划解决的问题:
- 通过静态文件维护系统通知内容
- 手动触发后端读取并同步通知到数据库
- 支持已有通知的修改
- 支持已有通知的撤销
- 保持用户侧已读状态不因通知内容更新而丢失
本计划不替代主通知系统计划,而是在其基础上增加“静态通知同步”能力。
关联文档:
docs/plans/notification-system-plan.md
2. 范围
2.1 In Scope
- 新增静态通知配置目录
- 定义静态通知 YAML 协议
- 定义对应的 Pydantic schema
- 实现后端扫描、校验、upsert 同步逻辑
- 实现对主通知的修改和撤销
- 新增手动触发同步脚本
2.2 Out of Scope
- 系统级离线推送
- 自动监听文件变化并实时同步
- 复杂运营后台
3. 现有代码基线
当前仓库已经有可直接复用的“静态配置 -> 数据库初始化”模式:
- 静态配置目录:
backend/src/core/config/static/database/ - 现有 YAML:
llm_catalog.yamlsystem_agents.yaml
- 现有加载与校验:
backend/src/core/config/initial/init_data.py - 现有 CLI:
backend/src/core/runtime/cli.py - 现有脚本:
infra/scripts/dev-migrate.sh
通知同步应复用这套模式的核心思路:
- YAML 文件作为配置源
- Pydantic schema 做强校验
- 后端显式执行同步
- 数据库使用 upsert 语义更新
但通知同步不应直接并入 init-data/bootstrap 默认流程,因为通知内容属于持续变更的数据,不是纯启动种子数据。
4. 目录设计
建议新增静态通知目录:
backend/src/core/config/static/notification/
└── notifications/
├── welcome_bonus.yaml
├── maintenance_2026_04.yaml
└── ...
第一阶段不增加总索引文件,直接扫描 notifications/*.yaml。
原因:
- 少一层维护成本
- 避免“文件内容”和“索引文件”双源不一致
- 更适合增量增加通知文件
5. 数据模型变更
要支持“静态文件和数据库中的同一条通知”建立稳定映射,notifications 表需要增加来源标识字段。
建议新增字段:
sourcesource_keysource_versioncontent_hash
建议约束:
UNIQUE(source, source_key)
5.1 字段职责
source- 通知来源
- 当前静态通知固定为
static
source_key- 静态通知唯一键
- 例如
welcome_bonus - 用于可靠 upsert
source_version- 配置版本号
- 用于审计和变更追踪
content_hash- 标准化内容摘要
- 用于判断文件内容是否发生变化
5.2 推荐表结构补充
在 notifications 表基础上补充:
ALTER TABLE notifications
ADD COLUMN source VARCHAR(32) NOT NULL DEFAULT 'manual',
ADD COLUMN source_key VARCHAR(128),
ADD COLUMN source_version INTEGER,
ADD COLUMN content_hash VARCHAR(64);
CREATE UNIQUE INDEX uq_notifications_source_source_key
ON notifications(source, source_key)
WHERE source_key IS NOT NULL;
说明:
manual可作为非静态创建通知的默认来源- 静态同步通知统一使用
source='static'
6. 静态通知 YAML 协议
每个 YAML 文件描述一条主通知及其投递目标。
推荐结构:
notification:
source_key: welcome_bonus
version: 1
type: system
status: published
published_at: 2026-04-10T08:00:00Z
title: 新用户欢迎通知
body: 你已获得注册奖励,可前往积分中心查看。
payload:
deleted: false
action: open_route
route: /points
entity_id: null
tab: balance
targets:
mode: all_users
指定用户示例:
notification:
source_key: maintenance_2026_04
version: 3
type: system
status: published
title: 系统维护通知
body: 今晚 23:00 到 23:30 进行维护。
payload:
action: none
targets:
mode: user_ids
user_ids:
- 11111111-1111-1111-1111-111111111111
- 22222222-2222-2222-2222-222222222222
7. Pydantic Schema 设计
静态通知文件必须先经过强校验,不能直接把 YAML 转 dict 入库。
建议新增模块:
backend/src/core/config/notification/static_schema.py
建议 schema:
StaticNotificationDefinitionStaticNotificationTargetsStaticNotificationFile
payload 不重新定义,直接复用现有通知协议里的 schema:
NotificationPayloadNoneNotificationPayloadRouteNotificationPayloadUrl
7.1 StaticNotificationDefinition 职责
source_key- 静态通知唯一键
version- 配置版本号
type- 通知类型,当前默认
system
- 通知类型,当前默认
statusdraft/published/revoked
deleted- 显式软删除主通知
published_at- 发布时间
title/body/payload- 通知内容
7.2 StaticNotificationTargets 职责
modeall_users或user_ids
user_ids- 仅当
mode='user_ids'时允许
- 仅当
7.3 校验约束
source_key必填且全局唯一version >= 1status只允许draft/published/revokeddeleted为可选布尔值payload必须符合现有通知 payload schematargets.mode='all_users'时不允许传user_idstargets.mode='user_ids'时user_ids必填且不能为空
8. 同步语义
8.1 新建
当数据库中不存在 (source='static', source_key=...) 时:
- 创建
notifications - 按目标规则写入
user_notifications
8.2 修改
当数据库中已存在同一 source_key 时:
- 更新
notifications.title/body/payload/status/published_at/source_version/content_hash - 保留已有
user_notifications - 不重置
is_read/read_at
这是强规则:
- 修改主通知内容,不影响用户已读状态
8.3 撤销
当 YAML 中:
notification.status = revoked
则同步时:
- 更新
notifications.status='revoked' - 写入
revoked_at - 不删除
user_notifications
8.4 统一删除
本阶段支持两种明确的下线方式:
- 在 YAML 中显式写
deleted: true - 执行同步时使用
--prune,将文件中已不存在的静态通知软删除
-
deleted: true语义: -
设置
notifications.deleted_at -
不删除既有
user_notifications -
--prune语义: -
扫描范围内缺失的静态通知会被软删除
-
不会删除非
source='static'的通知
默认情况下,不因为文件消失自动删库。
原因:
- 文件误删风险高
- 容易把版本控制操作误解释为业务删除
如果只是想临时停止用户可见,优先用:
status: revoked
如果想做统一下线并保留审计主记录,可用:
deleted: true
8.5 目标用户变更
默认采用保守策略:
- 新增目标用户时,补插入
user_notifications - 被移出目标集合的用户,不自动删除既有
user_notifications
原因:
- 防止误操作删除已投递历史
- 与“通知一旦发出就保留用户侧记录”的语义更一致
如果执行同步时显式加上 --reconcile-targets,则:
- 当前目标集合之外的既有
user_notifications会被删除
9. 后端实现方案
9.1 模块位置
建议新增:
backend/src/core/config/notification/
├── static_schema.py
└── static_sync.py
不建议把通知同步继续堆进 core/config/initial/init_data.py。
原因:
init_data.py当前更适合 bootstrap seed- 通知同步是持续执行的配置同步任务
- 语义上应独立
9.2 组件职责
static_schema.py- 定义 YAML 文件的 Pydantic schema
static_sync.py- 扫描目录
- 读取 YAML
- 校验 schema
- 计算差异
- 执行 upsert
现有通知模块中建议补充内部同步能力:
v1/notifications/repository.py- 补充按
source/source_key查询与 upsert
- 补充按
v1/notifications/service.py- 补充内部同步逻辑与事务边界
9.3 日志与错误
遵循现有后端规则:
- 使用
core.logging - 不使用
print - YAML 校验失败要明确报错并中止
- 数据库 upsert 失败要中止,不吞错
10. CLI 与脚本方案
10.1 后端 CLI
在 backend/src/core/runtime/cli.py 中新增命令:
sync-notifications
建议调用方式:
PYTHONPATH=backend/src uv run python -m core.runtime.cli sync-notifications
建议参数:
--path--source-key--dry-run--prune--reconcile-targets
危险行为必须显式开启,不默认启用。
10.2 infra 脚本
新增:
infra/scripts/register-notifications.sh
脚本风格复用 infra/scripts/dev-migrate.sh:
- 读取
.env - 通过
uv run python -m core.runtime.cli sync-notifications调用后端 CLI
建议用法:
./infra/scripts/register-notifications.sh
./infra/scripts/register-notifications.sh --dry-run
./infra/scripts/register-notifications.sh --source-key welcome_bonus
./infra/scripts/register-notifications.sh --prune --reconcile-targets
11. 与现有通知系统的关系
这条静态同步链路只负责:
- 把 YAML 中的通知定义注册到数据库
- 更新通知主记录
- 撤销通知主记录
- 为目标用户补齐接收关系
它不替代现有通知 API:
- 用户列表、未读数、已读接口仍走现有通知系统
- Flutter 端仍然从现有通知 API 和 Realtime 获取数据
如果通知内容被静态同步更新,而前台需要即时看到变更,建议在 Realtime 中补充:
notification_updated
否则前台只能在下次 HTTP 拉取时看到更新后的内容。
12. 实施清单
- 为
notifications表增加source/source_key/source_version/content_hash - 增加
(source, source_key)唯一约束 - 新增
backend/src/core/config/static/notification/notifications/目录 - 定义静态通知 YAML 的 Pydantic schema
- 实现 YAML 扫描、加载、校验与 upsert 同步逻辑
- 为通知模块补充按
source/source_key查询与更新能力 - 在
core.runtime.cli中新增sync-notifications命令 - 新增
infra/scripts/register-notifications.sh - 支持
--prune和--reconcile-targets - 视需要补充
notification_updatedRealtime 事件 - 编写最小测试和 dry-run 校验
13. 验收标准
- 新增一个 YAML 文件后,可成功同步出对应主通知记录
- 相同
source_key的 YAML 再次同步时,会更新主通知而不是插入重复记录 - 修改
title/body/payload后,再同步可反映到数据库 - 用户侧已读状态在主通知内容更新后保持不变
- 将
status改为revoked后,再同步可使通知在用户列表中失效 - 将
deleted改为true后,再同步可使通知从用户列表和未读数中消失 --dry-run可输出计划变更而不写库--prune可将文件中已不存在的静态通知软删除--reconcile-targets可严格对齐目标用户集合- YAML 结构不合法时同步失败,并给出明确错误
- 脚本可按全量或按
source_key手动触发同步
14. 测试要求
后端至少覆盖:
- YAML schema 校验
- 新建通知同步
- 已有通知更新同步
- 撤销同步
- 显式软删除同步
- 相同
source_key幂等 upsert - 更新主通知时不重置
user_notifications.is_read/read_at - 新增目标用户时补插入接收关系
- 被移出目标集合时不删除既有接收关系
--reconcile-targets下删除多余接收关系--prune下软删除缺失静态通知
脚本至少验证:
- 正常执行 CLI
--dry-run不写库--source-key只同步指定通知
15. 后续扩展条件
只有在真实需求出现时,再考虑:
- 用删除文件触发软删除
- 通过后台页面管理静态通知
- 将静态通知同步纳入更完整的发布工作流