FoneGo 消息发送全生命周期

需求文档 V2.4 · 逐条模板触发、动作、路由与终态

一条消息只有一条业务主记录

业务事件触发后,后端匹配模板并生成站内信;PUSH 是该站内信的一条渠道投递记录。点击、跳转、完成和撤销都围绕同一个 messageId 更新。

站内信是消息事实PUSH 是触达渠道业务状态决定终态

销售端与运营后台高保真原型

统一评审入口顶部可切换 Sales H5 / Operations Console;原型内交互可直接点击。
新窗口打开原型 ↗

原型元素逐项说明:前端、接口与库表

原型元素数据来源 / 计算规则后端接口数据库字段前端交互
首页消息红点+数字totalUnread = count(is_read=0 AND is_valid=1 AND expire_time>now AND create_time>=now-60d);只计当前登录 userGid,最大显示 99+GET /message-center/summary
返回 totalUnread 与 categoryUnreadMap
user_message_center_info.user_gid
is_readis_valid、新增 expire_timecategory
totalUnread>0 显示红点和数字;为 0 整体隐藏;账号切换时清缓存并重新请求
Message 页分类图标角标每个分类独立计算:categoryUnreadMap[category];口径与首页一致,只统计该 category 的未读且未过期消息/summary 同一响应,禁止前端分页累加推算category + is_read + expire_time有未读才显示分类小红点/数字;切换分类不自动已读
手机通知权限横幅来自 Android/iOS 系统通知权限,不以服务端 Token 存在代替系统权限检查H5 通过 Native Bridge getNotificationPermission();“去开启”调 openNotificationSettings();无需业务后端无服务端表更新;关闭当天存客户端 notification_banner_dismissed:{userGid}:{yyyyMMdd}未开权限时显示“开启手机消息通知,接收佣金到账、订单变更消息”;可关闭,当天不再展示,次日重新检查
通知横幅“去开启”系统权限关闭时可操作Native Bridge 打开 App 系统通知设置页从系统设置返回页面时重新检查;已开启则立即隐藏,仍关闭则保留
消息列表与 60 天范围仅返回 publish_time >= now-60d 的当前用户消息,按 publish_time 倒序分页GET /message-center/messages?category=&pageNo=&pageSize=publish_gidcategorypublish_titlepublish_contentpublish_timeis_readexpire_timebusiness_status首页 20 条,上拉分页;60 天前的数据即使后台还保留,客户端也不展示
卡片时间消息实际发布时间,不是模板创建时间列表响应 publishTimeuser_message_center_info.publish_time;异常历史数据为空时才兼容 create_time当天显示 HH:mm,当年非当天显示 MM-dd,跨年显示 yyyy-MM-dd
未读卡片is_read=0 且当前未过期点击时 POST /message-center/{messageId}/resolve首次点击更新 is_read=1read_time=nowis_click=1click_time=now标题加粗+红点;点击后立即本地去红点,总数与分类数 -1,接口失败则回滚
已读卡片is_read=1 且未过期仍调 resolve 获取实时路由不重复覆盖 read_time;点击可更新 click_time整体置灰、无红点,仍允许跳转当前业务页
过期卡片expire_time<=now 或业务状态使原动作失效调 resolve,后端重查当前业务对象,返回 CURRENT_STATE_ROUTE新增 business_status=EXPIRED/SUPERSEDEDexpire_time;不计入未读与已读一样置灰,显示 Expired;可点击,只跳当前页面/列表,不执行旧 actionCode,并提示状态已变更
一键已读当前用户所有未读且未过期消息POST /message-center/read-all
入参可选 category;空表示全部
批量更新 is_read=1read_time=nowupdate_time=now;条件 user_gid + is_read=0 + is_valid=1 + expire_time>now二次确认可免;成功后清除首页、所有分类角标和可见卡片红点
Sending Records 接收人从用户消息快照与接收人主数据组合,不从 PUSH 响应字符串解析GET /admin/message-center/records新增/冻结 receiver_gidreceiver_mobilereceiver_rolereceiver_type(SALE/USER);列表手机号脱敏后台列表展示 User GID、手机号、接收人角色/类型;权限用户可查看完整号码

数据保留与删除规则

数据范围保留期查询与展示到期处理
销售端站内信客户端只展示近 60 天列表接口强制 publish_time>=now-60d,前端不允许绕过60 天后只从客户端隐藏,后台还可追溯至 1 年
后台消息与发送记录近 1 年(365天)后台默认查 30 天,可选时间最早为 now-365d每日凌晨清理 publish_time/create_time<now-365d 的用户消息、投递尝试和发送响应;分批物理删除并记录清理批次/数量
模板版本与运营审计不随消息记录一起删除用于追溯谁修改、发布、禁用和撤销按公司审计政策独立保留,不适用 1 年消息记录删除任务

消息事件、模板与用户原型

同一个配置工作台选择事件后,模板配置和用户看到的 PUSH / 站内信同步变化。
① 消息事件31 events
② 模板配置message_template
站内信PUSH
ENABLED · version 1
③ PUSH 与站内信原型用户最终效果
Messages
FoneGo · now

Just now · Unread

PUSH 点击只携带 messageId;进入详情后重新查询业务状态和 allowedActions。

当前事件闭环规则

事件负责人 幂等键 去重 / 频控 有效期 完成条件 取消 / 撤销条件

模板配置字段与启停规则

字段定义后端执行规则
category站内信列表的用户分类,不等于业务表名本期固定为 MEMBERS、DEVICES、PAYMENTS、COMMISSION、SYSTEM;不设 Orders 首页入口,订单结果消息归 PAYMENTS
eventType业务事件唯一码,表示“什么状态变成了什么状态”业务事务提交后发出;eventId 唯一;消息服务按 eventType + receiverRole + country + version 匹配模板
actionCode用户要做的动作语义,例如 REVIEW_APPLICATION、EDIT_APPLICATION、RETRY_PAYMENT、VIEW_COMMISSION用于按钮文案、权限与可操作性校验;不直接代表 URL;终态后 allowedActions=[]
routeCode前端白名单页面标识,由 RouteRegistry 映射 H5 路由后端只返回 routeCode + routeParams;禁止模板填任意 URL;每次点击先重查当前业务状态
enabled模板是否接受新事件禁用后不再发送。新事件记录 SKIPPED_TEMPLATE_DISABLED,不生成站内信、不创建 PUSH 任务;历史消息不删除
禁用/启用边界防止停用、恢复时重复发送已 CREATED/RETRY_WAIT 任务是否取消由“同步取消待发”开关决定,默认取消;已 SENT 不可收回;重新启用只处理之后的新事件,禁用期间不自动补发

成员消息模板详细定义

eventType / 用途精确触发规则接收人 / categoryactionCoderouteCode / 参数终止与过期
new.member.create
通知审核人有新的销售入职申请
仅 Agent/Supervisor 新建或重新提交,且 sale_info.review_status 从 null/2/3 变为 0,业务事务成功后触发;经理及以上创建直接为 1,不触发可审批该申请的直属上级/运营审核角色
MEMBERS
REVIEW_APPLICATIONTEAM_MEMBER_SUBMISSIONS
{reviewStatus:0,saleGid}
H5 基线 /memberList,本期增加受控筛选
可操作条件是 review_status=0。变为 1/2/3 时原消息立即关闭动作,分别记 COMPLETED_APPROVED / SUPERSEDED_REJECTED / SUPERSEDED_RETURNED,不统称 EXPIRED
new.member.return
资料被驳回,需修改后重提
审核操作成功,review_status: 0/2 → 3,且 reviewRemark 非空被驳回销售员;如实际业务要求创建人代修,需权限评审后切换
MEMBERS
EDIT_APPLICATIONMEMBER_APPLICATION_EDIT
{saleGid,reviewStatus:3}
重提为 0 时 COMPLETED_RESUBMITTED;变为 1/2 时 SUPERSEDED;对象删除时 EXPIRED
new.member.reject
通知入职申请被拒绝
审核操作成功,review_status: 0 → 2,且 reviewRemark 非空申请人;创建人是否同时通知由产品确认
MEMBERS
VIEW_REJECTIONMEMBER_APPLICATION_RESULT
{saleGid,reviewStatus:2}
重新提交变为 0 时原结果记 SUPERSEDED_RESUBMITTED;历史可查但不可执行旧动作
new.member.joined
通知上级新成员已入团
review_status: 0/2 → 1 且审核事务提交;代码已有 FirebaseTypeCode.New_Member_Joined创建人/新直属上级
MEMBERS
VIEW_MEMBERTEAM_MEMBER_DETAIL
{saleGid}
前端需改造 /memberDetail 支持 saleGid,不再传整个 item JSON
成员离职/删除后不打开旧详情,转 Team Members 列表并提示状态已变更
member.role.change
通知新上级成员角色变更
角色变更事务成功,roleLevel/parentSaleGid 任一发生有效变化新直属上级
MEMBERS
VIEW_MEMBERTEAM_MEMBER_DETAIL {saleGid}下一次角色/团队变更时保留历史快照;点击始终查当前权限
role.change
通知本人自己的角色变更
角色变更事务成功且 oldRole != newRole被调整销售员
MEMBERS
VIEW_PROFILEMY_PROFILE消息长期保留;无需过期,个人中心展示最新角色
team.updated
通知本人团队/上级变更
parentSaleGid/teamId 在有效业务事务中变更被调整销售员
MEMBERS
VIEW_PROFILEMY_PROFILE长期保留;跳转展示当前团队
member.offboard
下属离职通知
成员在职状态 active → offboard 的事务成功离职前直属上级
MEMBERS
VIEW_MEMBER_LISTTEAM_MEMBERS结果型消息,不配操作按钮;保留历史
member.freeze
冻结/冻结预警
正常 → 冻结,或批处理首次进入“预计 1 天后冻结”阈值;预警与结果必须两个 eventType,不可共用一码本人+直属上级
MEMBERS
VIEW_ACCOUNT_STATUSACCOUNT_STATUS / TEAM_MEMBER_DETAIL风险解除时 COMPLETED_RECOVERED;未解除按频控催办,不因过了 1 天而静默失效
member.overdue.high
逾期率超限
计算口径确认后,只在逾期率跨过阈值时触发;Excel 中“45% < x <= 40%”区间无效,开发前必须由业务重新给出上下限本人+直属上级
MEMBERS
VIEW_OVERDUE_ORDERS本人 MY_ORDERS / 上级 TEAM_ORDERS恢复至阈值内后 COMPLETED_RECOVERED;点击永远查当前订单与权限
member.sleep
近 N 天无销量休眠预警/结果
批处理首次满足“近 25 天销量=0”触发预警;真正休眠状态变更用独立结果事件本人+直属上级
MEMBERS
VIEW_SALES_ACTIVITYMY_ORDERS / TEAM_ORDERS产生新有效销售或休眠解除时 COMPLETED_RECOVERED

订单 / 支付消息模板详细定义

eventType / 用途精确触发规则接收人 / categoryactionCoderouteCode终止与过期
user.create.rejected
客户授信失败
授信审核结果从处理中转为 REJECTED,且结果事务已提交订单所属销售员
PAYMENTS
VIEW_CREDIT_RESULTCUSTOMER_DETAIL {userGid,orderGid}结果型,不自动过期;重新授信生成新事件,旧结果保留
user.create.returned
授信资料驳回需修改
授信状态处理中 → RETURNED,必须有驳回原因订单所属销售员
PAYMENTS
EDIT_CUSTOMER_APPLICATIONCUSTOMER_APPLICATION_EDIT {userGid,orderGid}重提后原卡片 COMPLETED_RESUBMITTED;审核结果出现后 SUPERSEDED
down_payment_successful
分期订单首付成功
同一 paymentAttemptId 状态从 PROCESSING → SUCCESS,并且订单首付入账订单所属销售员
PAYMENTS
VIEW_ORDERMY_ORDER_DETAIL {orderGid}
H5 /billDetails
结果型,COMPLETED;对象不可见时回退 /billList
down_payment_failed
分期订单首付失败并可重试
paymentAttemptId 进入 FAILED,且失败码属于可重试类型订单所属销售员
PAYMENTS
RETRY_DOWN_PAYMENTDOWN_PAYMENT {orderGid,paymentAttemptId}任一后续支付成功、订单取消或支付窗口关闭时终止;成功为 COMPLETED,取消/超时为 EXPIRED
full_payment_successful
全款订单支付成功
全款 paymentAttemptId PROCESSING → SUCCESS,订单状态已入账订单所属销售员
PAYMENTS
VIEW_ORDERFULL_ORDER_DETAIL {orderGid}COMPLETED;详情不可见时回退全款订单列表
full_payment_failed
全款支付失败
全款 paymentAttemptId 进入 FAILED 且失败码可重试订单所属销售员
PAYMENTS
RETRY_FULL_PAYMENTFULL_PAYMENT {orderGid,paymentAttemptId}支付成功、订单取消或支付窗口结束时关闭动作

佣金消息模板详细定义

eventType用途与触发规则接收人 / categoryactionCoderouteCode终止与过期
daily.bonus.pay.success
daily.bonus.pay.fail
日佣金单次支付尝试 PROCESSING → SUCCESS/FAILED;按 bonusPaymentId 幂等,重试不重复生成失败消息收款销售员本人
COMMISSION
成功 VIEW_DAILY_COMMISSION
失败 VIEW_PAYMENT_FAILURE
MY_COMMISSION_DAILY
H5 /MyComm 的 Daily 视图
成功为 COMPLETED;失败消息在后续成功后 SUPERSEDED_SUCCESS,不应继续催办
mon.bonus.pay.success
mon.bonus.pay.fail
销售员本人月佣金支付批次结果转为 SUCCESS/FAILED;按 period + receiverGid + paymentBatchId 幂等收款销售员本人
COMMISSION
VIEW_MONTHLY_COMMISSIONMY_COMMISSION_MONTHLY
/MyComm
成功 COMPLETED;失败在后续补发成功后 SUPERSEDED_SUCCESS
leader.mon.bonus.pay.success
leader.mon.bonus.pay.fail
组长管理佣金对应月份的支付结果落库后触发收款组长本人
COMMISSION
VIEW_TEAM_COMMISSION_HISTORYTEAM_COMMISSION_HISTORY {period,role:LEADER}
H5 /MonthlyHistory
失败后成功则替换终态;历史月份可查
manager.mon.bonus.pay.success
manager.mon.bonus.pay.fail
经理管理佣金对应月份的支付结果落库后触发收款经理本人
COMMISSION
VIEW_MANAGER_COMMISSION_HISTORYTEAM_COMMISSION_HISTORY {period,role:MANAGER}角色变更后点击先做权限校验;无权限则转个人佣金页
country.mon.bonus.pay.success
country.mon.bonus.pay.fail
国家总监管理佣金对应月份的支付结果落库后触发收款国家总监本人
COMMISSION
VIEW_COUNTRY_COMMISSION_HISTORYTEAM_COMMISSION_HISTORY {period,role:COUNTRY_MANAGER}成功 COMPLETED;失败后补发成功则 SUPERSEDED_SUCCESS;按期间保留

设备物流消息模板详细定义

eventType触发规则接收人 / categoryactionCode / routeCode终止与过期
device.shipped设备调拨单 CREATED → SHIPPED,且物流发货事务成功发货人
DEVICES
VIEW_OUTGOING_SHIPMENT
DEVICE_OUTGOING_DETAIL {transferGid}
签收/取消后 COMPLETED;不可以其他调拨单覆盖
device.awaiting调拨单发货后首次进入 AWAITING_RECEIPT收货人
DEVICES
CONFIRM_RECEIPT
DEVICE_INCOMING_DETAIL {transferGid}
签收、拒收或调拨取消时关闭动作
device.receivedAWAITING_RECEIPT → RECEIVED;Excel 中 device.recieved 为拼写错误,需做旧码兼容映射发货人+收货人
DEVICES
VIEW_SHIPMENT
按角色分流 OUTGOING / INCOMING DETAIL
结果型 COMPLETED,保留历史
device.receipt.overduenow > receiptDueAt 且调拨仍为 AWAITING_RECEIPT;仅在首次超时及配置的催办节点触发发货人+收货人
DEVICES
VIEW_OVERDUE_SHIPMENT
按角色分流物流详情
签收/拒收/取消后 COMPLETED;后续催办按 transferGid + reminderStage 幂等

过期、已处理与旧 PUSH 点击规则

状态站内信展示PUSH 点击适合 FoneGo 的处理
ACTIVE / 仍可操作未读有红点,已读无红点;两者均可点上报 messageId 后由后端 resolve,返回当前 routeCode直达业务页,不经消息详情
COMPLETED / SUPERSEDED卡片灰置,展示“已处理/状态已变更”,不再展示原动作;允许点击查看当前状态系统通知栏中的旧 PUSH 不可撤回;点击后 resolve 返回 CURRENT_STATE_ROUTE跳对应列表的当前状态 Tab,顶部提示“This item has already been processed”;不执行旧动作
EXPIRED / 真过期灰置,展示 Expired 标签及原因,不计入未读数;允许点击点击请求 resolve,不直接使用卡片/PUSH 里的旧 URL如还有安全页,跳当前列表/详情并提示“This notification has expired”;后端不返回旧 actionCode;无可见页时返首页
REVOKED卡片灰置不可点,显示“Withdrawn”,不计入未读后端返回 REVOKED,不开业务操作页回首页或安全列表页,展示“This notification has been withdrawn”

前端、后端、数据库时序图

前端业务/消息后端数据库PUSH 服务
阶段
业务后端
消息后端
数据库
Android / H5
触发
业务事务提交后发布 eventId、eventType、bizId、receiver
校验事件幂等、接收人和触发条件
event_log 写 RECEIVED
生成
匹配启用模板,渲染标题、正文、分类、动作和 routeCode
message 写 UNREAD + CREATED
delivery 写 PUSH/CREATED
投递
提交 messageId 与摘要到 PUSH 服务
delivery 更新 SENT 或 FAILED,保存 providerMessageId/errorCode
系统通知栏展示 PUSH
点击
鉴权并查询消息详情、业务最新状态和 allowedActions
message.read_status=READ
event_log 写 OPENED
PUSH/列表点击后请求详情
跳转
业务接口再次校验权限和对象状态
根据 routeCode 返回白名单页面及参数
event_log 写 ACTION_CLICKED / ROUTE_RESULT
按 routeCode 打开 H5/原生页;失败回退消息详情
终态
业务完成、过期或撤销后发布结果事件
关闭动作、取消未发送任务,返回只读结果
message.business_status 更新终态
delivery 待发任务 CANCELLED
展示已完成/已失效/已撤销,不再显示原操作按钮

消息状态机

CREATED消息已生成
UNREAD未查看
READ已查看
ACTION_REQUIRED仍需业务操作
COMPLETED业务已完成
EXPIRED超过有效期
REVOKED业务撤销
CANCELLED待发送任务取消

每一步的系统行为

步骤 前端 后端 数据库 结果状态
1 触发 无主动动作 业务事务成功后发送事件;eventId 必须唯一 message_event_log 记录 RECEIVED;重复事件返回原结果 RECEIVED
2 模板 按 eventType、receiverRole、country、language、version 匹配启用模板 message_template 保存文案、渠道、分类、routeCode 和候选动作 MATCHED / NO_TEMPLATE
3 站内信 下次拉取列表可见 渲染变量并按 eventId + receiverId + messageType 幂等创建 user_message 新增主记录及内容快照 UNREAD + CREATED
4 PUSH 无需在线 模板 channel 包含 PUSH 时创建渠道任务;不影响站内信 message_delivery 新增 PUSH/CREATED CREATED
5 发送 通知栏展示标题和摘要 查 token,调用 FCM;失败按策略重试 delivery 写 attemptCount、SENT/FAILED、providerMessageId、errorCode SENT / FAILED
6 点击 PUSH 或列表点击后请求消息详情 从登录态校验接收人;重查业务最新状态 首次打开写 readAt;日志写 OPENED READ;业务状态不变
7 跳转 按 routeCode 打开白名单页面;失败回退消息详情 返回 routeCode、routeParams、fallback、allowedActions 日志写 ACTION_CLICKED、ROUTE_SUCCESS/FAILED PROCESSING 或保持
8 终态 显示只读结果;隐藏旧按钮 收到完成/过期/撤销事件后关闭动作并取消待发任务 主记录写 COMPLETED/EXPIRED/REVOKED;待发 delivery 写 CANCELLED 终态,不再催办

核心接口与库表

对象 接口/表 关键字段 职责
业务事件 POST /internal/message-events eventId, eventType, bizType, bizId, receiver, templateParams 业务系统提交消息事实
模板 message_template templateCode, eventType, category, channels, titleTpl, bodyTpl, routeCode, actionCode, status, version 统一配置文案、渠道和候选跳转
消息主记录 user_message messageId, eventId, receiverId, readStatus, businessStatus, contentSnapshot, bizId, expireAt 站内信事实和用户状态
渠道投递 message_delivery messageId, channel, status, attemptCount, providerMessageId, errorCode, sentAt 记录 PUSH/SMS 投递结果
操作日志 message_event_log messageId, eventType, source, actionCode, routeResult, occurredAt 记录接收、打开、点击、跳转和终态
客户端 GET /messages
GET /messages/{id}
POST /messages/{id}/read
筛选条件 / messageId / source 列表、详情、已读和实时动作
关键约束:已读只表示用户看过,不代表业务已处理;PUSH 失败不影响站内信;模板只配置候选动作,最终按钮由详情接口根据最新业务状态返回。

研发与测试评审清单

评审域 研发必须确认 测试必须覆盖 上线门槛
事件接入 事务提交后发事件;eventId 生成规则;Outbox 或等价可靠投递 重复、乱序、延迟、缺字段、错误接收人 31 个事件均有负责人和联调用例
模板管理 配置存储、版本、启停、变量校验、灰度范围 缺变量、停用模板、错误版本、语言回退 生产模板经过产品与业务双人复核
站内信 幂等键、分页、未读数、权限范围、数据保留周期 多端已读、并发读取、翻页、账号切换、越权 消息主记录可按 eventId/messageId 追溯
PUSH Token 生命周期、重试、频控、静默时段、供应商错误映射 无 Token、失效 Token、通知关闭、前后台与杀进程 PUSH 失败不阻塞业务且不丢站内信
路由 RouteRegistry 白名单、参数校验、版本兼容、fallback 缺参、对象删除、无权限、旧版本、未登录恢复 禁止模板直接配置任意 URL
终态 完成/撤销/过期事件;动作关闭;待发任务取消 旧 PUSH 点击、完成后重复操作、撤销后列表与详情 不存在已完成仍可操作或继续催办
监控告警 创建成功率、投递成功率、延迟、失败码、积压、重复率 告警阈值和故障演练 具备 messageId/eventId/providerMessageId 全链路查询
发布回滚 数据库兼容、模板先后顺序、客户端最低版本、功能开关 灰度组、回滚后旧消息可读、关闭 PUSH 保留站内信 先站内信、后 PUSH;可按事件快速停用

范围、依赖与待决策项

类型 内容 负责人 评审要求
本期范围 31 个事件;模板版本管理;站内信列表/详情/已读;PUSH;白名单跳转;终态关闭;投递与操作日志 产品 / 前后端 全部进入开发排期
非本期 营销人群圈选、用户自定义通知偏好、多语言运营后台、删除历史交易消息 产品 不得顺带扩展
依赖 业务事件、用户与角色关系、销售 App Token、H5 稳定路由、FCM、业务对象实时状态接口 各系统负责人 联调前提供环境与数据
阻塞决策 风险阈值、设备签收超时、审核责任人、支付失败重试权限、模板运营入口由现有后台还是一期直接入库 业务 / 产品 / 技术负责人 开发启动前冻结结论
发布策略 按事件开关灰度;先开启站内信验证数据,再开启 PUSH;异常时关闭对应事件 PUSH,不删除消息主记录 技术负责人 上线方案和回滚脚本评审通过

代码与 Ghana 数据库核查结论

核查项已有事实本期结论
销售端落地页/memberList 已存在,当前是 Team Members 团队层级列表,没有“成员提交”页签复用现有路由,新增 Submissions 视图及受控筛选参数;旧版不支持时回退普通 Team Members
模板代码模型message_center_info 已定义 PUSH / 首页 / 消息中心文案、跳转链接、优先级和有效标记保留可复用字段,增加事件码、分类、变量 Schema、版本、审批状态、有效期和受控 routeCode,不允许任意 URL
用户消息代码模型user_message_center_info 已定义批次、用户、发布状态、文案快照、已读/已点击、时间和有效标记可作为兼容基线;需补齐 eventId 幂等、category、expireAt、businessStatus、revokeReason 和 templateVersion
Ghana 在线库eos.firebase_app_user_token 保存终端 Token;eos.log_app_msg 已有约 15 万条发送日志,保存内容、入参和响应现表没有独立 sendStatus / retryCount / providerMessageId / errorCode,不足以支持可运营的发送记录与重试
Ghana 站内信表当前可见 schema 中未发现 message_center_info / user_message_center_info开发前确认是未部署、位于其他数据源,还是需在 Ghana 新建;未确认前不写死物理库名

运营后台:人工发送消息工具

配置项短信 SMSPUSH站内信
接收人输入手机号,每行一个或逗号分隔;支持 Ghana 本地号 0XXXXXXXXX 和 E.164 +233XXXXXXXXXIMEI,每行一个或逗号分隔;后端用 IMEI 解析到唯一有效绑定的 userGid/saleGid,不允许前端自行映射
内容输入内容必填,1–500 字符;显示 GSM-7/Unicode 类型、字符数和预计短信分段数标题必填 1–50 字符;内容必填 1–200 字符;可选 routeCode,禁止输入任意 URL标题必填 1–50 字符;内容必填 1–500 字符;必选 category;可选 routeCode
目标校验去空格、去重、本地号归一为 +233;校验 E.164 长度与号段;黑名单/退订号码禁止发送IMEI 必须为 15 位数字并通过 Luhn;必须存在唯一 active 绑定、用户未冻结、存在有效 Firebase Token;无 Token 列为不可发IMEI 必须为 15 位数字并通过 Luhn;解析到唯一有效 userGid/saleGid;无 PUSH Token 不影响站内信发送
预览与确认点击“校验接收人”后,后端返回有效、重复、无效、无权限与预计占用额度;发送前二次确认“渠道、有效人数、额度、内容摘要”;校验 token 5 分钟有效,超时重新校验

人工发送额度、权限与幂等

规则明确口径后端实现验收
单次人数每次请求最多 10 个去重后的接收人;超过 10 个整单拒绝,不截断前 10 个前端提前拦截,后端再次强校验;以归一化手机号或解析后 receiverGid 去重11 个、重复后 10 个、空值混入均覆盖
每日总额度运营后台“发送消息”工具每个自然日共 1000 个发送单位,短信+PUSH+站内信合并计算,时区使用 Ghana Africa/AccraRedis/DB 原子预占额度;预占成功后才创建批次;创建失败释放,已进入渠道发送不返还并发两个请求不可突破 1000;跨日归零以 Accra 日期为准
额度计数单位接收人 × 渠道。同一人发 PUSH+站内信计 2 条;长短信按实际分段数 × 接收人数计数校验接口返回 estimatedQuota;发送接口再计算并比对,不信任前端传值10 人发双渠道=20;2 段短信发 10 人=20
权限查看、校验、发送分权;只有 MESSAGE_MANUAL_SEND 可确认发送后端校验操作人、国家/业务线数据权限;短信敏感内容可选双人审核无权限用户看不到发送按钮,直调接口仍 403
幂等与防重前端生成 requestId;同 requestId 重试返回原批次,不重复占额度/发送唯一索引 operatorGid + requestId;确认页连续点击锁定按钮超时重试、双击、网络断开后重试均只有一个批次

人工发送接口与库表

对象定义关键字段
POST /admin/message-center/manual/validate校验接收人、内容、路由、权限和额度,不发送channel,recipients,title,content,category,routeCode,routeParams;返回 validationToken、valid/invalid 明细、estimatedQuota、remainingQuota
POST /admin/message-center/manual/send使用 5 分钟内的 validationToken 确认发送requestId + validationToken;返回 batchNo、acceptedCount、quotaUsed
manual_message_send_batch(新增)一次后台人工发送批次及审计主记录batch_no,request_id,operator_gid/name,channels,recipient_count,quota_units,title/content_snapshot,category,route_code/params,status,create_time
manual_message_send_item(新增)每个接收人每个渠道一条明细batch_no,channel,input_value_masked,imei_hash,receiver_gid,receiver_mobile,receiver_role/type,segment_count,status,error_code,provider_message_id,send_time
现有消息表站内信仍生成 user_message_center_info;PUSH 仍写渠道投递记录/eos.log_app_msg必须回填 manual batchNo 以便 Sending Records 从批次追溯到用户消息和供应商响应

运营后台:模板、发送记录与撤销

功能页面与操作后端 / 数据规则权限与验收
模板列表按事件、分类、渠道、状态搜索;查看当前版本和最近发布人返回事件绑定、生效范围、站内信/PUSH 开关产品、运营可查;审计角色可看历史
新建 / 复制 / 修改配置 category、受众、文案、变量、渠道、有效期、频控、routeCode 及参数;支持预览和白名单试发草稿可编辑;已发布版本不覆盖,修改产生新版本;校验变量 Schema 与路由白名单编辑与发布分权;任意 URL、空文案和缺失变量禁止提交
审核 / 发布 / 停用DRAFT → REVIEWED → PUBLISHED;支持按事件紧急停用和回滚生效时仅允许一个匹配版本;停用只阻止新消息,不删除历史快照产品+业务双人复核;全操作写审计日志
发送记录按 messageId、eventId、批次、用户、渠道、状态和时间检索;查看模板快照、尝试、失败原因、已读/点击/跳转轨迹用户消息与渠道投递分开;每次尝试记录 attemptNo、providerMessageId、errorCode、requestAt、responseAt敏感参数脱敏;支持 messageId 一键追溯;禁止修改历史
失败重试展示自动重试次数和下次时间;有权人可人工重试可恢复错误仅 FAILED_RETRYABLE 且未过期/未撤销可重试;无效 Token 停止重试;不重复生成站内信记录操作人和原因;同一投递任务幂等
撤销可按单条消息或批次撤销,必填原因并二次确认;预览影响人数和待发任务数事务内写 REVOKED、revokeAt/revokeBy/reason,取消 CREATED/RETRY_WAIT 投递;已发 PUSH 无法从通知栏收回撤销后未读数立即重算,卡片灰置且不可点;旧 PUSH 点击进“已撤销”兜底
监控与审计看板展示生成成功率、投递成功率、延迟、积压、重复率和撤销量阈值告警;模板、试发、重试、停用、撤销全量审计监控数据可对账;审计记录不可删改

新旧链路迁移与模板治理

阶段 执行规则 数据与回滚
1 基线确认 优先评估扩展现有 message_center_infouser_message_center_info;未经架构评审不得直接建设第三套消息表 确认字段映射、历史数据保留和唯一写入服务
2 双读验证 新接口只读验证历史消息,旧接口继续服务;比对未读数、权限和内容 异常立即关闭新接口开关,不修改历史数据
3 事件灰度 按 eventType 开关逐个接入;先站内信,数据正确后再打开 PUSH 同一事件只允许一个生产者,禁止新旧链路重复发送
4 模板发布 DRAFT → REVIEWED → PUBLISHED;产品与业务双人复核;发布后版本不可覆盖 保留历史版本和发布人,可按事件回滚上一版本
5 全量与下线 31 个事件验收通过后关闭旧 PUSH 入口和注释残留调用 历史消息继续可读;回滚只切写入开关,不删除主记录

验收标准

场景 预期结果
同一 eventId 重复提交 只生成一条用户消息,不重复创建 PUSH 任务
无 token 或 PUSH 失败 站内信仍为 UNREAD;delivery 准确记录失败原因
用户点击 PUSH 进入对应 messageId 详情;消息变 READ,业务状态不变
业务完成后点击旧 PUSH 详情展示 COMPLETED,只提供查看结果,不显示旧操作
消息撤销或过期 前端展示失效原因;后端关闭动作,取消未发送任务
路由缺参、旧版本或越权 不打开任意 URL;回退消息详情并记录 ROUTE_FAILED
账号切换 A 账号消息、pendingRoute 和缓存不得在 B 账号展示;重新鉴权失败后清除
冻结用户 无法登录时不跳登录后 H5;展示原生限制说明或 approved fallback
风险持续与恢复 持续风险按频控提醒;恢复后取消待发任务并更新旧详情为已恢复
管理角色聚合 同团队同类事件按配置聚合,不逐成员产生 PUSH 噪音;站内信仍可追溯成员明细