一条消息只有一条业务主记录
业务事件触发后,后端匹配模板并生成站内信;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_gidis_read、is_valid、新增 expire_time、category | 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_gid、category、publish_title、publish_content、publish_time、is_read、expire_time、business_status | 首页 20 条,上拉分页;60 天前的数据即使后台还保留,客户端也不展示 |
| 卡片时间 | 消息实际发布时间,不是模板创建时间 | 列表响应 publishTime | user_message_center_info.publish_time;异常历史数据为空时才兼容 create_time | 当天显示 HH:mm,当年非当天显示 MM-dd,跨年显示 yyyy-MM-dd |
| 未读卡片 | is_read=0 且当前未过期 | 点击时 POST /message-center/{messageId}/resolve | 首次点击更新 is_read=1、read_time=now、is_click=1、click_time=now | 标题加粗+红点;点击后立即本地去红点,总数与分类数 -1,接口失败则回滚 |
| 已读卡片 | is_read=1 且未过期 | 仍调 resolve 获取实时路由 | 不重复覆盖 read_time;点击可更新 click_time | 整体置灰、无红点,仍允许跳转当前业务页 |
| 过期卡片 | expire_time<=now 或业务状态使原动作失效 | 调 resolve,后端重查当前业务对象,返回 CURRENT_STATE_ROUTE | 新增 business_status=EXPIRED/SUPERSEDED、expire_time;不计入未读 | 与已读一样置灰,显示 Expired;可点击,只跳当前页面/列表,不执行旧 actionCode,并提示状态已变更 |
| 一键已读 | 当前用户所有未读且未过期消息 | POST /message-center/read-all入参可选 category;空表示全部 | 批量更新 is_read=1、read_time=now、update_time=now;条件 user_gid + is_read=0 + is_valid=1 + expire_time>now | 二次确认可免;成功后清除首页、所有分类角标和可见卡片红点 |
| Sending Records 接收人 | 从用户消息快照与接收人主数据组合,不从 PUSH 响应字符串解析 | GET /admin/message-center/records | 新增/冻结 receiver_gid、receiver_mobile、receiver_role、receiver_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 与站内信原型用户最终效果
FoneGo · now
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 / 用途 | 精确触发规则 | 接收人 / category | actionCode | routeCode / 参数 | 终止与过期 |
|---|---|---|---|---|---|
new.member.create通知审核人有新的销售入职申请 | 仅 Agent/Supervisor 新建或重新提交,且 sale_info.review_status 从 null/2/3 变为 0,业务事务成功后触发;经理及以上创建直接为 1,不触发 | 可审批该申请的直属上级/运营审核角色 MEMBERS | REVIEW_APPLICATION | TEAM_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_APPLICATION | MEMBER_APPLICATION_EDIT{saleGid,reviewStatus:3} | 重提为 0 时 COMPLETED_RESUBMITTED;变为 1/2 时 SUPERSEDED;对象删除时 EXPIRED |
new.member.reject通知入职申请被拒绝 | 审核操作成功,review_status: 0 → 2,且 reviewRemark 非空 | 申请人;创建人是否同时通知由产品确认 MEMBERS | VIEW_REJECTION | MEMBER_APPLICATION_RESULT{saleGid,reviewStatus:2} | 重新提交变为 0 时原结果记 SUPERSEDED_RESUBMITTED;历史可查但不可执行旧动作 |
new.member.joined通知上级新成员已入团 | review_status: 0/2 → 1 且审核事务提交;代码已有 FirebaseTypeCode.New_Member_Joined | 创建人/新直属上级 MEMBERS | VIEW_MEMBER | TEAM_MEMBER_DETAIL{saleGid}前端需改造 /memberDetail 支持 saleGid,不再传整个 item JSON | 成员离职/删除后不打开旧详情,转 Team Members 列表并提示状态已变更 |
member.role.change通知新上级成员角色变更 | 角色变更事务成功,roleLevel/parentSaleGid 任一发生有效变化 | 新直属上级 MEMBERS | VIEW_MEMBER | TEAM_MEMBER_DETAIL {saleGid} | 下一次角色/团队变更时保留历史快照;点击始终查当前权限 |
role.change通知本人自己的角色变更 | 角色变更事务成功且 oldRole != newRole | 被调整销售员 MEMBERS | VIEW_PROFILE | MY_PROFILE | 消息长期保留;无需过期,个人中心展示最新角色 |
team.updated通知本人团队/上级变更 | parentSaleGid/teamId 在有效业务事务中变更 | 被调整销售员 MEMBERS | VIEW_PROFILE | MY_PROFILE | 长期保留;跳转展示当前团队 |
member.offboard下属离职通知 | 成员在职状态 active → offboard 的事务成功 | 离职前直属上级 MEMBERS | VIEW_MEMBER_LIST | TEAM_MEMBERS | 结果型消息,不配操作按钮;保留历史 |
member.freeze冻结/冻结预警 | 正常 → 冻结,或批处理首次进入“预计 1 天后冻结”阈值;预警与结果必须两个 eventType,不可共用一码 | 本人+直属上级 MEMBERS | VIEW_ACCOUNT_STATUS | ACCOUNT_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_ACTIVITY | MY_ORDERS / TEAM_ORDERS | 产生新有效销售或休眠解除时 COMPLETED_RECOVERED |
订单 / 支付消息模板详细定义
| eventType / 用途 | 精确触发规则 | 接收人 / category | actionCode | routeCode | 终止与过期 |
|---|---|---|---|---|---|
user.create.rejected客户授信失败 | 授信审核结果从处理中转为 REJECTED,且结果事务已提交 | 订单所属销售员 PAYMENTS | VIEW_CREDIT_RESULT | CUSTOMER_DETAIL {userGid,orderGid} | 结果型,不自动过期;重新授信生成新事件,旧结果保留 |
user.create.returned授信资料驳回需修改 | 授信状态处理中 → RETURNED,必须有驳回原因 | 订单所属销售员 PAYMENTS | EDIT_CUSTOMER_APPLICATION | CUSTOMER_APPLICATION_EDIT {userGid,orderGid} | 重提后原卡片 COMPLETED_RESUBMITTED;审核结果出现后 SUPERSEDED |
down_payment_successful分期订单首付成功 | 同一 paymentAttemptId 状态从 PROCESSING → SUCCESS,并且订单首付入账 | 订单所属销售员 PAYMENTS | VIEW_ORDER | MY_ORDER_DETAIL {orderGid}H5 /billDetails | 结果型,COMPLETED;对象不可见时回退 /billList |
down_payment_failed分期订单首付失败并可重试 | paymentAttemptId 进入 FAILED,且失败码属于可重试类型 | 订单所属销售员 PAYMENTS | RETRY_DOWN_PAYMENT | DOWN_PAYMENT {orderGid,paymentAttemptId} | 任一后续支付成功、订单取消或支付窗口关闭时终止;成功为 COMPLETED,取消/超时为 EXPIRED |
full_payment_successful全款订单支付成功 | 全款 paymentAttemptId PROCESSING → SUCCESS,订单状态已入账 | 订单所属销售员 PAYMENTS | VIEW_ORDER | FULL_ORDER_DETAIL {orderGid} | COMPLETED;详情不可见时回退全款订单列表 |
full_payment_failed全款支付失败 | 全款 paymentAttemptId 进入 FAILED 且失败码可重试 | 订单所属销售员 PAYMENTS | RETRY_FULL_PAYMENT | FULL_PAYMENT {orderGid,paymentAttemptId} | 支付成功、订单取消或支付窗口结束时关闭动作 |
佣金消息模板详细定义
| eventType | 用途与触发规则 | 接收人 / category | actionCode | routeCode | 终止与过期 |
|---|---|---|---|---|---|
daily.bonus.pay.successdaily.bonus.pay.fail | 日佣金单次支付尝试 PROCESSING → SUCCESS/FAILED;按 bonusPaymentId 幂等,重试不重复生成失败消息 | 收款销售员本人 COMMISSION | 成功 VIEW_DAILY_COMMISSION失败 VIEW_PAYMENT_FAILURE | MY_COMMISSION_DAILYH5 /MyComm 的 Daily 视图 | 成功为 COMPLETED;失败消息在后续成功后 SUPERSEDED_SUCCESS,不应继续催办 |
mon.bonus.pay.successmon.bonus.pay.fail | 销售员本人月佣金支付批次结果转为 SUCCESS/FAILED;按 period + receiverGid + paymentBatchId 幂等 | 收款销售员本人 COMMISSION | VIEW_MONTHLY_COMMISSION | MY_COMMISSION_MONTHLY/MyComm | 成功 COMPLETED;失败在后续补发成功后 SUPERSEDED_SUCCESS |
leader.mon.bonus.pay.successleader.mon.bonus.pay.fail | 组长管理佣金对应月份的支付结果落库后触发 | 收款组长本人 COMMISSION | VIEW_TEAM_COMMISSION_HISTORY | TEAM_COMMISSION_HISTORY {period,role:LEADER}H5 /MonthlyHistory | 失败后成功则替换终态;历史月份可查 |
manager.mon.bonus.pay.successmanager.mon.bonus.pay.fail | 经理管理佣金对应月份的支付结果落库后触发 | 收款经理本人 COMMISSION | VIEW_MANAGER_COMMISSION_HISTORY | TEAM_COMMISSION_HISTORY {period,role:MANAGER} | 角色变更后点击先做权限校验;无权限则转个人佣金页 |
country.mon.bonus.pay.successcountry.mon.bonus.pay.fail | 国家总监管理佣金对应月份的支付结果落库后触发 | 收款国家总监本人 COMMISSION | VIEW_COUNTRY_COMMISSION_HISTORY | TEAM_COMMISSION_HISTORY {period,role:COUNTRY_MANAGER} | 成功 COMPLETED;失败后补发成功则 SUPERSEDED_SUCCESS;按期间保留 |
设备物流消息模板详细定义
| eventType | 触发规则 | 接收人 / category | actionCode / routeCode | 终止与过期 |
|---|---|---|---|---|
device.shipped | 设备调拨单 CREATED → SHIPPED,且物流发货事务成功 | 发货人 DEVICES | VIEW_OUTGOING_SHIPMENTDEVICE_OUTGOING_DETAIL {transferGid} | 签收/取消后 COMPLETED;不可以其他调拨单覆盖 |
device.awaiting | 调拨单发货后首次进入 AWAITING_RECEIPT | 收货人 DEVICES | CONFIRM_RECEIPTDEVICE_INCOMING_DETAIL {transferGid} | 签收、拒收或调拨取消时关闭动作 |
device.received | AWAITING_RECEIPT → RECEIVED;Excel 中 device.recieved 为拼写错误,需做旧码兼容映射 | 发货人+收货人 DEVICES | VIEW_SHIPMENT按角色分流 OUTGOING / INCOMING DETAIL | 结果型 COMPLETED,保留历史 |
device.receipt.overdue | now > 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
delivery 写 PUSH/CREATED
投递
提交 messageId 与摘要到 PUSH 服务
delivery 更新 SENT 或 FAILED,保存
providerMessageId/errorCode
系统通知栏展示 PUSH
点击
鉴权并查询消息详情、业务最新状态和 allowedActions
message.read_status=READ
event_log 写 OPENED
event_log 写 OPENED
PUSH/列表点击后请求详情
跳转
业务接口再次校验权限和对象状态
根据 routeCode 返回白名单页面及参数
event_log 写 ACTION_CLICKED / ROUTE_RESULT
按 routeCode 打开 H5/原生页;失败回退消息详情
终态
业务完成、过期或撤销后发布结果事件
关闭动作、取消未发送任务,返回只读结果
message.business_status 更新终态
delivery 待发任务 CANCELLED
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 /messagesGET /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 新建;未确认前不写死物理库名 |
运营后台:人工发送消息工具
| 配置项 | 短信 SMS | PUSH | 站内信 |
|---|---|---|---|
| 接收人输入 | 手机号,每行一个或逗号分隔;支持 Ghana 本地号 0XXXXXXXXX 和 E.164 +233XXXXXXXXX | IMEI,每行一个或逗号分隔;后端用 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/Accra | Redis/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_info、user_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 噪音;站内信仍可追溯成员明细 |