米米商聊动态可见范围拆解:受众规则、置顶与访问校验
一条动态设为“公开”是否意味着陌生人也能看到把一条私密动态置顶是否会让原本没有权限的人看到这些问题涉及两个不同维度谁可以查看以及有权限的人看到怎样的展示顺序。本文以米米商聊的动态可见范围为例拆解受众规则与置顶的关系并给出一个可运行的 JavaScript 规则模型。产品事实依据为产品方提供的动态说明与界面素材以下代码、字段和测试数据均为独立教学示例不是产品源码也不用于证明实际后台已经采用某种访问控制架构。内容与配图使用 AI 辅助示例已独立运行核验。1. 先确认“公开”指向的对象现有手机端资料确认了四种动态可见范围可见范围已确认的产品含义公开所有好友可见不等于面向全网私密仅自己可见指定好友选择可以查看的好友不给谁看排除不希望其查看的好友资料还说明已发布动态可以调整可见范围置顶仍受原有可见范围约束。修改受众与修改正文是两种操作不能由“可调整范围”推断“发布后正文可以直接编辑”。以上事实来自手机端资料。桌面截图没有提供完整的动态权限流程本文不将手机步骤直接扩写成桌面端操作指南。当一个产品在好友关系范围内使用“公开”一词时文案应把参照集合说明白。否则用户容易把“好友内公开”与“任何登录用户可见”“全网可访问”等不同规则混为一谈。2. 用集合表达受众比堆叠布尔字段更清楚在一份用于讲解的规则模型中设作者为 A当前输入的好友集合为 F被选中的名单为 S公开的受众可以表达为 A 加上 F。私密的受众是 A。指定好友的受众是 A 加上 F 与 S 的交集。不给谁看的受众是 A 加上 F 去除 S 后的成员。为什么“指定好友”使用交集而不是直接放行名单里的所有 ID因为这个示例把“是好友”也作为条件。即使输入名单中混入一个陌生人的 ID它也不能因此获得查看权限。这里的 F 是传给示例函数的关系快照。实际产品在添加或删除好友后如何处理历史动态、受众按发布时还是读取时的关系计算现有材料不足以给出完整结论这些是需要单独确认的业务规则不能让示例替产品作决定。图独立规则模型示意。人物标识与名单均为虚构数据不是 App 实际界面或内部结构。相比isPublic、isPrivate、hasAllowList、hasExcludeList等字段同时存在单个mode可以避免出现“既公开又私密”这种冲突状态。规则名单再承担与该模式对应的含义。这是本文的建模选择不是对产品实际字段的披露。3. 置顶改变展示优先级不增加受众假设甲可以看某条动态乙被排除。置顶之后这条动态对甲的展示优先级可以变化乙不应该仅因为它被置顶就获得查看权限。因此在类似系统设计中适合让访问判断先回答“是否允许查看”展示排序再处理“已经允许的内容怎样排列”。不要写成“只要置顶就展示给所有人”也不要让排序组件承担权限判断。对于米米商聊这一段的功能依据是“置顶仍受可见范围限制”。具体排序算法、置顶数量、缓存和同步时效没有在本轮资料中逐项核实本文不补写这些实现细节。4. 一个可运行的独立规则函数下面只验证一次读取所需的受众条件。输入是普通数据对象ID 为非空、没有首尾空格的字符串名单必须是完整的字符串数组。未知模式或缺少关系数据时示例返回false。constallowedModesnewSet([friends,self,selected,excluded]);functionisId(value){returntypeofvaluestringvalue.length0valuevalue.trim();}functionisIdList(value){// 展开后检查也会发现稀疏数组里的空位。returnArray.isArray(value)[...value].every(isId);}functioncanReadPost(post,viewerId,friendIds){if(!post||!isId(post.authorId)||!isId(viewerId))returnfalse;constaudiencepost.audience;if(!audience||!allowedModes.has(audience.mode))returnfalse;if(!isIdList(audience.ids)||!isIdList(friendIds))returnfalse;if(viewerIdpost.authorId)returntrue;constfriendsnewSet(friendIds);if(!friends.has(viewerId))returnfalse;constselectednewSet(audience.ids);switch(audience.mode){casefriends:returntrue;caseself:returnfalse;caseselected:returnselected.has(viewerId);caseexcluded:return!selected.has(viewerId);default:returnfalse;}}这里用Set表达成员集合并通过has()判断归属重复 ID 不会产生额外成员。相关语言行为可查 MDNSet。函数没有读取pinned因为这个模型把它定义为展示属性。它也不按昵称判断身份两个显示名称相同的账号不应因为名字相同就共享权限。这段函数不是完整的权限服务。它没有实现登录认证、拉黑规则、动态删除、媒体资源访问、跨端缓存或并发修改也不能把前端返回false视为后台已经保护了资源。5. 测试应覆盖被拒绝的情形以下代码接在前一段后可用 Node.js 运行。除正常好友外还检查陌生人混入名单、空名单、未知模式、缺少关系数据以及置顶前后结果是否一致。constassertrequire(node:assert/strict);constfriends[u1,u2];constmakePost(mode,ids[])({authorId:author,audience:{mode,ids},pinned:false});constcases[[作者可读私密内容,makePost(self),author,friends,true],[公开对好友可见,makePost(friends),u1,friends,true],[公开不放行陌生人,makePost(friends),outsider,friends,false],[私密不放行好友,makePost(self),u1,friends,false],[指定名单中的好友可读,makePost(selected,[u1]),u1,friends,true],[未指定的好友不可读,makePost(selected,[u1]),u2,friends,false],[名单中的陌生人仍不可读,makePost(selected,[outsider]),outsider,friends,false],[指定空名单不放行好友,makePost(selected),u1,friends,false],[未被排除的好友可读,makePost(excluded,[u2]),u1,friends,true],[被排除的好友不可读,makePost(excluded,[u2]),u2,friends,false],[排除空名单保留好友,makePost(excluded),u1,friends,true],[空访问者ID不放行,makePost(friends),,friends,false],[未知模式不放行,makePost(unknown),u1,friends,false],[错误名单类型不放行,makePost(excluded,u2),u1,friends,false],[缺少关系数据不放行,makePost(friends),u1,undefined,false],[稀疏名单不放行,makePost(excluded,newArray(1)),u1,friends,false]];for(const[name,post,viewer,snapshot,expected]ofcases){assert.equal(canReadPost(post,viewer,snapshot),expected,name);}for(constmodeofallowedModes){constpostmakePost(mode,[u1]);for(constviewerof[author,u1,u2,outsider]){assert.equal(canReadPost({...post,pinned:true},viewer,friends),canReadPost(post,viewer,friends),置顶不扩大受众);}}constoriginalmakePost(friends);assert.equal(canReadPost(original,u1,friends),true);assert.equal(canReadPost(makePost(excluded,[u1]),u1,friends),false);assert.equal(canReadPost(original,u1,[u2]),false);console.log(18组规则检查通过);本次实际核验环境为 Node.js v24.19.0。16组明确预期的样例加上置顶不变性、受众或关系快照变化两组检查全部通过。测试范围是本文纯函数不包含产品客户端、浏览器集成或真实接口。其中“缺少关系数据不放行”与“没有好友”不是同一个界面状态。类似系统可以在读取失败时显示错误或重试提示而不是把失败直接解释为用户主动选择了空受众。6. 从规则模型到实际访问还要接上什么规则保存成功以后下一次读取应根据系统明确的策略进行校验。若只是把内容从客户端列表隐藏知道原动态地址的人仍可能尝试直接请求正文、图片等资源也需要与相应访问规则衔接。这属于通用系统设计建议。OWASP 授权指南建议默认拒绝、在每次请求中验证权限并在合适的位置执行检查。本文没有对米米商聊实际服务器作安全测评也不据此断言其存在或不存在某种问题。修改范围还应区分“当前是否允许读取”与“别人以前是否已经看过”。一次受众调整不能自动收回此前已经看见、截图或另行保存的内容因此产品解释应避免把范围设置宣传成不可传播的保证。拆解动态可见范围时可以沿着三个问题核对可见性的参照集合是什么修改范围后哪一次读取使用哪份规则置顶是否只作用于已经允许查看的内容。把这些条件说明白功能文案、程序判断和测试预期才容易对齐。