站内通知与执行恢复
# 站内通知与执行恢复
当前提供租户通知模板、站内发送任务、本人收件箱和可恢复的后台投递。邮件、短信、任意 HTTP 回调和用户脚本尚未接入;不能将站内回执解释为外部渠道送达。
# 开通与启动
- 显式执行
bash scripts/fago.sh migrate,使用当前 schema 对应的 API、worker 和前端。迁移失败按 数据库迁移 处理,不直接修改 dirty 或校验和。 - 平台管理员在可编辑套餐开通
infra.notifications,再分配给目标租户。迁移不会扩大已有套餐或兼容套餐的能力上限。 - 租户角色分别授予
infra:template:read/create/update/delete、infra:notification:read/send/cancel/replay与infra:notification:inbox。保留的tenant_admin包含这些权限,仍受套餐约束;普通账号需要显式授予收件箱权限。 - API 与 worker 使用相同业务数据库,分别执行
bash scripts/fago.sh api和bash scripts/fago.sh worker。两者都要求现有 Redis 运行依赖可用;对象存储可以关闭,站内通知仍运行。 - 重新获取导航后打开「通知模板」「通知发送记录」或「我的通知」。平台身份不能代入租户发送或查看正文。
正式 worker 默认装配进程内 Event transport,也可显式选择 Redis Streams,配置和运维见 可靠消息后端与积压观测。它等待消费确认;内存传递本身不持久,可靠恢复由同业务库 outbox/inbox 负责。Event 不承诺跨进程广播或全局顺序。Redis Streams 入口会检查本机 AOF 确认与可靠前提,不自动降级;其他消息后端仍待接入。
# 模板与发送
模板使用小写编码、名称、标题、正文和启停状态。只支持 {{name}} 简单占位符,变量名以 ASCII 字母开头,随后允许字母、数字和下划线,最长 32 字符。最多 20 个不同变量,不支持函数、条件、循环或动态执行。标题最多 200 字符且 800 字节,正文最多 4096 UTF-8 字节;每租户最多 1000 个有效模板。
编码创建后不可变。编辑或删除必须携带当前版本,旧版本返回 409001;先刷新核对再修改,不能自动覆盖。软删除后可以新建同编码,但新记录使用新 ID。修改、停用和删除只影响新的发送受理,已经受理的内容快照保持不变。
在模板上选择「发送」,填写同租户有效收件账号 ID 和全部变量。变量必须与模板声明严格一致,每值最多 256 UTF-8 字节。渲染后标题仍限制 200 字符,正文非空且最多 8192 字节。变量作为纯文本,不会再次解释占位符或 HTML。
发送命令必须携带模板 ID/版本、收件账号 ID、变量和 16–64 位小写字母/数字/下划线/短横线幂等键。页面为每次新发送生成随机键;首次提交后冻结完整命令,失败点击重试会重用原键和原内容。关闭再打开会创建新命令,遇到未知结果时可先查发送记录。
幂等范围为当前租户和发送账号。同一键、同一完整输入返回原任务;同键不同输入返回 409002。精确重试不依赖模板仍启用或仍存在,但仍复核调用者当前会话与发送权限。数据库内受理快照、outbox 和审计一起提交,网络请求失败不能靠另造一个键猜测之前未受理。
# 收件箱与隐私
「我的通知」只查询当前登录账号,不接受 recipientId 或 tenantId 覆盖选择器。拥有发送记录管理权限也不能读取其他人的正文。前端按纯文本显示标题和正文,查看不会执行 HTML、脚本或图片请求;只有显式「标记已读」才更新回执。
受理和实际投递都会核对收件账号仍属于当前租户、未删除且启用。登录锁定、首次改密和暂时没有会话不妨碍接收站内通知。账号在受理后停用或删除,消费会留下死信,恢复后可按权限人工重放;不自动把已删账号复活。
管理任务返回模板身份、收件账号 ID、状态、版本与执行元数据,不返回正文、变量、幂等键、摘要、对象路径或租约 token。操作审计也只记录资源身份、状态、渠道和版本。后台使用独立执行范围,actorId 对外为空,关联号为 worker:<eventId>,不会把发送人或收件人伪装成执行者。
# 重试、取消和死信恢复
发送任务状态为待投递、已投递或已取消;消息派发和消费另外显示各自状态、尝试次数、稳定业务码和租约截止。派发已确认不等于业务已投递,以任务「已投递」和本人回执为准。租户停用、到期或撤销套餐能力后后台也会拒绝执行,不能借旧用户权限继续处理。
后台每轮最多五次,按持久退避和 24 小时截止恢复,单次本地事务有 10 秒截止、租约 30 秒。临时依赖故障可重试;参数、权限和不存在等明确拒绝直接保留死信。执行回调禁止网络 IO。站内回执、任务终态、后台审计、执行历史和 inbox done 共用事务;重复投递或提交应答丢失由原事件去重。
待投递任务可以按当前任务版本取消。已投递内容不可撤回。取消后事件仍保留,迟到消费只确认完成,不生成回执。死信恢复需同时携带任务版本和选中派发/消费记录的版本,只恢复 dead;不清除成功记录或创建新事件身份。并发或迟到恢复返回版本冲突。
「执行历史」保留每次认领的结果、时间和业务码。人工重放可重置本轮次数,但不会覆盖旧历史;认领版本持续推进。租约过期的旧 running 标记为「结果未确认」,不能据此断言业务一定没有执行。原始错误链与正文不会进入历史。
当前提供 SQL 待办与通知受理积压指标;Redis 引用保留与安全回收见 可靠消息后端。SQL 去重/终态/历史记录持续保留,SQL 自动到期删除、外部告警渠道及 broker 全量监控尚未接入。定时任务管理 已提供独立运行和恢复入口;不要通过删除 inbox、outbox 或历史表来解决积压。范围与实际验证见 M5 实施记录。
# 接口入口
统一根路径 /api/admin/v1/infra,全部沿用当前认证、Origin、生产 JWE 和统一错误出口:
| 路径 | 方法与用途 |
|---|---|
/notice-templates、/notice-templates/{id} | GET 列表、POST 创建、PUT 更新、DELETE 按版本软删 |
/notice-tasks | GET 管理元数据、POST 按幂等键受理 |
/notice-tasks/{id}/cancel | POST 按任务版本取消 |
/notice-tasks/{id}/replay | POST 按任务/执行双版本恢复死信 |
/notice-tasks/{id}/attempts | GET 有界执行历史 |
/notice-inbox、/notice-inbox/{id}/read | GET 本人通知、POST 本人标记已读 |
列表最大每页 100,模板支持名称关键词;发送记录、执行历史和收件箱不接受关键词或账号覆盖条件。详细请求和响应以仓库 api/openapi.yaml 为准。
受理发送任务(幂等键在请求体;须相应写权限):
POST /api/admin/v1/infra/notice-tasks
Origin: http://127.0.0.1:5174
X-App-Request: admin
Authorization: Bearer <access>
Content-Type: application/json
{
"idempotencyKey": "notice-demo-001",
"templateId": "1",
"variables": {}
}