开启免打扰后,没有听见提示声,是消息没有收到,还是提醒被减少了?群里有人 @ 自己时,又该怎样理解这项设置?
本文以米米商聊的免打扰功能为观察对象,面向产品设计与客户端开发读者,拆解消息接收、应用内提醒规则和系统通知条件之间的关系,再用一个可运行的 JavaScript 示例检查容易混淆的边界。
产品事实依据来自产品方提供的手机端功能说明。代码、字段和情境数据为独立教学示例,不是产品源码;未据此判断其实际消息服务或推送架构。内容及配图使用 AI 辅助,示例已经独立运行核验。
1. 免打扰改变的是提醒方式
目前资料支持以下描述:
| 功能事实 | 需要保留的边界 |
|---|---|
| 免打扰主要调整提醒方式,消息仍正常接收 | 不能把这项设置解释为阻止接收消息 |
| 群内 @ 提醒存在例外 | 不能承诺开启后任何情况都没有提醒 |
| 聊天可以置顶 | 置顶是查找与展示安排,不应直接等同于提醒强度 |
“消息仍正常接收”描述的是免打扰设置的作用,不是断网、进程退出或后台受限时仍然实时送达的承诺。
本文也不把未读红点、未读数字等具体样式作为功能结论。它们需要结合当前客户端与版本核对,不能仅凭某种样式没有出现就判断“消息没收到”。桌面端的完整免打扰流程,本轮没有对应的逐项实测资料,不直接套用手机操作步骤。
2. 消息与提醒至少有三种状态
对类似聊天产品进行设计时,可以把一个事件拆成以下问题:
- 消息是否已经进入客户端的数据处理流程?
- 应用根据会话偏好与提及规则,是否准备请求提醒?
- 系统是否允许并实际呈现这次通知?
这三个问题不应只用一个 success 表示。例如,消息已经进入会话,应用选择不请求系统通知,是一个明确的提醒策略结果;它不等于消息数据处理失败。应用请求了通知,也不等于系统一定已经呈现,更不等于用户已经看见或理解了内容。

图:通用设计模型。提醒请求被抑制,不代表已到达的消息从会话中消失;不是 App 实际界面或内部链路。
对日志、测试和反馈材料而言,记录“收到事件”“规则决定”“系统请求结果”通常比只记录“通知成功/失败”更有解释力。具体日志字段如何设计仍应根据实际系统决定,本文不将示例字段说成产品已有能力。
3. 用独立函数表达提醒优先级
下面只处理已经交给客户端的消息事件,返回是否建议请求一次系统通知,不发送通知,也不处理消息网络接收。
示例选择以下规则:本人发送、会话不匹配、已经提醒过、当前会话可见时,不额外请求系统通知;系统通知条件不允许时不请求;免打扰抑制普通消息,但可通过 mentionOverride 显式决定是否保留对本人提及的例外。
其中“当前会话可见时不额外请求”是本文的示例策略,不是对米米商聊当前行为的断言。mentionOverride 同样是讲解用配置;产品中具体的 @ 处理应以当前客户端规则为准。
function validId(value) {
return typeof value === "string" && value.length > 0 && value === value.trim();
}
function notificationPlan(message, preferences, environment) {
const no = reason => ({
requestSystemNotice: false, reason });
if (!message || !preferences || !environment) return no("invalid-input");
const ids = [message.id, message.senderId, message.conversationId,
preferences.currentUserId, preferences.conversationId];
if (!ids.every(validId)) return no("invalid-input");
if (!Array.isArray(message.mentionedIds) ||
![...message.mentionedIds].every(validId)) return no("invalid-input");
const flags = [preferences.dnd, preferences.mentionOverride,
environment.systemAllowed, environment.conversationVisible,
environment.alreadyNotified];
if (!flags.every(value => typeof value === "boolean")) return no("invalid-input");
if (message.conversationId !== preferences.conversationId) return no("wrong-conversation");
if (message.senderId === preferences.currentUserId) return no("self-message");
if (environment.alreadyNotified) return no("already-notified");
if (environment.conversationVisible) return no("conversation-visible");
if (!environment.systemAllowed) return no("system-not-allowed");
const mentionsMe = message.mentionedIds.includes(preferences.currentUserId);
const exception = mentionsMe && preferences.mentionOverride;
if (preferences.dnd && !exception) return no("conversation-dnd");
return {
requestSystemNotice: true,
reason: preferences.dnd ? "mention-exception" : "regular-message"
};
}
这里故意保留 reason。单个 false 无法解释究竟是本人消息、当前正在查看、系统条件不允许,还是会话免打扰在起作用。
mentionedIds 用账号标识表达提及对象,不通过正文中的“@某个昵称”字符串判断。模型希望避免把引用文字或同名昵称直接当成一次有效提及;实际产品如何解析提及对象,仍需要其真实协议与实现资料。
alreadyNotified 是调用方提供的状态,函数没有维护去重记录。工程接入时,需要明确它代表哪一个动作已经完成,以及失败重试怎样处理;不能把“消息已接收”直接写成“提醒已完成”。
4. 系统通知条件应由平台适配层提供
应用内会话偏好和系统通知许可属于不同来源的条件。例如,在支持的 Web 环境中,Notification.permission 有 granted、denied、default 三种状态,不能把尚未授权的 default 当成允许。MDN:Notification.permission
Android 官方文档也说明了 Android 13 起的通知运行时权限及相关适用情形。Android:通知运行时权限
两种平台的接口并不相同。本文函数中的 systemAllowed 是为了让模型保持独立而使用的布尔输入,实际接入应由对应平台适配层计算,不代表米米商聊使用了 Web Notification API。
即使条件允许,函数返回 true 也只是建议提出请求。展示、声音和失败处理应由真实平台调用结果与对应设置决定,不能把纯函数测试当作系统通知呈现测试。
5. 用明确预期检查普通消息与例外
将下段代码接在前一段后,可以直接用 Node.js 运行。测试使用虚构账号和会话标识,不读取真实聊天内容。
const assert = require("node:assert/strict");
const M = extra => ({
id: "m1", senderId: "peer", conversationId: "room",
mentionedIds: [], ...extra });
const P = extra => ({
currentUserId: "me", conversationId: "room",
dnd: false, mentionOverride: true, ...extra });
const E = extra => ({
systemAllowed: true, conversationVisible: false,
alreadyNotified: false, ...extra });
const cases = [
["普通消息", M(), P(), E(), true, "regular-message"],
["免打扰普通消息", M(), P({
dnd:true}), E(), false, "conversation-dnd"],
["对本人提及例外", M({
mentionedIds:["me"]}), P({
dnd:true}), E(), true, "mention-exception"],
["提及其他人", M({
mentionedIds:["other"]}), P({
dnd:true}), E(), false, "conversation-dnd"],
["例外未启用", M({
mentionedIds:["me"]}), P({
dnd:true,mentionOverride:false}), E(), false, "conversation-dnd"],
["系统条件不允许", M(), P(), E({
systemAllowed:false}), false, "system-not-allowed"],
["提及不绕过系统条件", M({
mentionedIds:["me"]}), P({
dnd:true}), E({
systemAllowed:false}), false, "system-not-allowed"],
["当前会话可见", M(), P(), E({
conversationVisible:true}), false, "conversation-visible"],
["提及不改变可见会话策略", M({
mentionedIds:["me"]}), P({
dnd:true}), E({
conversationVisible:true}), false, "conversation-visible"],
["本人发送", M({
senderId:"me"}), P(), E(), false, "self-message"],
["会话不匹配", M({
conversationId:"other-room"}), P(), E(), false, "wrong-conversation"],
["已经提醒过", M(), P(), E({
alreadyNotified:true}), false, "already-notified"],
["字符串不是布尔配置", M(), P({
dnd:"false"}), E(), false, "invalid-input"],
["缺少有效用户ID", M(), P({
currentUserId:""}), E(), false, "invalid-input"],
["正文文字不是结构化提及", M({
body:"@me"}), P({
dnd:true}), E(), false, "conversation-dnd"]
];
for (const [name, message, prefs, env, allowed, reason] of cases) {
assert.deepEqual(notificationPlan(message, prefs, env),
{
requestSystemNotice:allowed, reason}, name);
}
for (const [, message, prefs, env] of cases) {
assert.deepEqual(notificationPlan(message, {
...prefs,pinned:true}, env),
notificationPlan(message, {
...prefs,pinned:false}, env), "置顶不改变本模型提醒策略");
}
const inputs = [M({
mentionedIds:["me"]}), P({
dnd:true}), E()];
const before = JSON.stringify(inputs);
notificationPlan(...inputs);
assert.equal(JSON.stringify(inputs), before, "不改写输入对象");
console.log("17组检查通过");
本次在 Node.js v24.19.0 实际运行了文章中的两个代码块:15组明确预期样例、置顶不变性和输入不被改写两组检查,全部通过。范围是独立函数,不包含产品客户端、真实消息服务或系统通知 API 的集成测试。
6. 实际验收怎样避免判断错对象
对类似聊天系统做验收时,适合分别记录以下观察结果:
| 观察项 | 可以回答什么问题 |
|---|---|
| 会话中是否出现目标消息 | 已到达事件是否进入对应界面的处理流程 |
| 规则返回的结果与原因 | 哪一个提醒条件在起作用 |
| 系统通知请求的实际结果 | 请求是否被平台接受或失败 |
| 用户是否打开并查看内容 | 不能只凭发出提醒就推断用户已看见 |
这些是验收建议,不是当前客户端日志或回执功能的清单。收到事件、提出提醒、系统呈现与人的实际阅读分别是什么状态,应按真实材料说明。
免打扰功能的专业拆解,重点是明确设置作用于哪一层。当接收与提醒被分别描述,用户容易理解为何普通消息没有提示却仍在会话中,开发者也更容易定位是会话规则、提及例外,还是平台通知条件影响了结果。