[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"i-ri:search-line":3,"i-ri:moon-line":8,"i-ri:sun-line":10,"i-ri:menu-3-line":12,"i-ri:close-line":14,"notes-module-items-im":16,"notes-page-im-01-message-state\u002F02-read-cursor":66,"i-ri:arrow-left-s-line":188,"i-ri:menu-2-line":190,"i-ri:arrow-right-s-line":192},{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":7},0,24,false,"\u003Cpath fill=\"currentColor\" d=\"m18.031 16.617l4.283 4.282l-1.415 1.415l-4.282-4.283A8.96 8.96 0 0 1 11 20c-4.968 0-9-4.032-9-9s4.032-9 9-9s9 4.032 9 9a8.96 8.96 0 0 1-1.969 5.617m-2.006-.742A6.98 6.98 0 0 0 18 11c0-3.867-3.133-7-7-7s-7 3.133-7 7s3.133 7 7 7a6.98 6.98 0 0 0 4.875-1.975z\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":9},"\u003Cpath fill=\"currentColor\" d=\"M10 7a7 7 0 0 0 12 4.9v.1c0 5.523-4.477 10-10 10S2 17.523 2 12S6.477 2 12 2h.1A6.98 6.98 0 0 0 10 7m-6 5a8 8 0 0 0 15.062 3.762A9 9 0 0 1 8.238 4.938A8 8 0 0 0 4 12\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":11},"\u003Cpath fill=\"currentColor\" d=\"M12 18a6 6 0 1 1 0-12a6 6 0 0 1 0 12m0-2a4 4 0 1 0 0-8a4 4 0 0 0 0 8M11 1h2v3h-2zm0 19h2v3h-2zM3.515 4.929l1.414-1.414L7.05 5.636L5.636 7.05zM16.95 18.364l1.414-1.414l2.121 2.121l-1.414 1.414zm2.121-14.85l1.414 1.415l-2.121 2.121l-1.414-1.414zM5.636 16.95l1.414 1.414l-2.121 2.121l-1.414-1.414zM23 11v2h-3v-2zM4 11v2H1v-2z\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":13},"\u003Cpath fill=\"currentColor\" d=\"M3 4h18v2H3zm6 7h12v2H9zm-6 7h18v2H3z\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":15},"\u003Cpath fill=\"currentColor\" d=\"m12 10.587l4.95-4.95l1.414 1.414l-4.95 4.95l4.95 4.95l-1.415 1.414l-4.95-4.95l-4.949 4.95l-1.414-1.415l4.95-4.95l-4.95-4.95L7.05 5.638z\"\u002F>",[17,22,26,30,34,38,42,46,50,54,58,62],{"path":18,"title":19,"meta":20},"\u002Fnotes\u002Fim\u002F01-message-state\u002F01-offline-sync","连接恢复了，离线消息为什么还没补齐？",{"date":-1,"tags":-1,"summary":-1,"lastUpdated":21},"2026-11-05T09:47:15.000Z",{"path":23,"title":24,"meta":25},"\u002Fnotes\u002Fim\u002F01-message-state\u002F02-read-cursor","手机读过了，电脑为何又显示未读？",{"date":-1,"tags":-1,"summary":-1,"lastUpdated":21},{"path":27,"title":28,"meta":29},"\u002Fnotes\u002Fim\u002F01-message-state\u002F03-pagination-search","聊天搜索跳回旧消息后，怎样继续翻页？",{"date":-1,"tags":-1,"summary":-1,"lastUpdated":21},{"path":31,"title":32,"meta":33},"\u002Fnotes\u002Fim\u002F01-message-state\u002F04-unread-refresh","消息没停，未读角标就不更新？",{"date":-1,"tags":-1,"summary":-1,"lastUpdated":21},{"path":35,"title":36,"meta":37},"\u002Fnotes\u002Fim\u002F02-connection-protocol\u002F01-stream-parsing","一次接收回调，为什么不是一条消息？",{"date":-1,"tags":-1,"summary":-1,"lastUpdated":21},{"path":39,"title":40,"meta":41},"\u002Fnotes\u002Fim\u002F02-connection-protocol\u002F02-request-response","长连接上的请求，怎样等到自己的响应？",{"date":-1,"tags":-1,"summary":-1,"lastUpdated":21},{"path":43,"title":44,"meta":45},"\u002Fnotes\u002Fim\u002F02-connection-protocol\u002F03-connection-lifecycle","登录一直在连接中，到底在等什么？",{"date":-1,"tags":-1,"summary":-1,"lastUpdated":21},{"path":47,"title":48,"meta":49},"\u002Fnotes\u002Fim\u002F03-message-content\u002F01-file-download","附件下载完了，为什么还是打不开？",{"date":-1,"tags":-1,"summary":-1,"lastUpdated":21},{"path":51,"title":52,"meta":53},"\u002Fnotes\u002Fim\u002F03-message-content\u002F02-image-send","图片上传到 100%，为什么还没发出去？",{"date":-1,"tags":-1,"summary":-1,"lastUpdated":21},{"path":55,"title":56,"meta":57},"\u002Fnotes\u002Fim\u002F03-message-content\u002F03-mentions","聊天 @提及怎样保存身份和位置？",{"date":-1,"tags":-1,"summary":-1,"lastUpdated":21},{"path":59,"title":60,"meta":61},"\u002Fnotes\u002Fim\u002F04-client-release\u002F01-flutter-build","Release 包为什么仍连着测试环境？",{"date":-1,"tags":-1,"summary":-1,"lastUpdated":21},{"path":63,"title":64,"meta":65},"\u002Fnotes\u002Fim","即时通信",{"date":-1,"tags":-1,"summary":-1,"lastUpdated":21},{"id":67,"type":68,"sourcePath":69,"routePath":23,"path":23,"title":24,"description":70,"updatedAt":21,"meta":71,"body":79,"toc":180},"bm90ZTpub3Rlcy9pbS8wMS1tZXNzYWdlLXN0YXRlLzAyLXJlYWQtY3Vyc29yLm1k","note","notes\u002Fim\u002F01-message-state\u002F02-read-cursor.md","同一账号在手机读完消息，电脑上的未读提醒也应该消失。但两端同时操作时，旧请求可能晚到；直接覆盖阅读位置，就可能让已读消息重新变成未读。",{"id":67,"type":68,"sourcePath":69,"slug":72,"title":24,"updatedAt":21,"description":70,"tags":73,"date":76,"comment":77,"summary":78,"lastUpdated":21},"im\u002F01-message-state\u002F02-read-cursor",[74,75],"frontend","IM","2026-07-18",true,"同步阅读边界时，迟到响应不能让位置倒退，同一成员的不同业务会话也不能混用光标。再把结果同步到数据库与界面。",{"type":80,"children":81},"root",[82,90,95,100,107,112,117,122,128,133,138,143,149,154,159,164,170,175],{"type":83,"tag":84,"props":85,"children":87},"element","h1",{"id":86},"手机读过了电脑为何又显示未读",[88],{"type":89,"value":24},"text",{"type":83,"tag":91,"props":92,"children":93},"p",{},[94],{"type":89,"value":70},{"type":83,"tag":91,"props":96,"children":97},{},[98],{"type":89,"value":99},"项目用已读光标同步进度。光标描述某个会话读到了哪条消息、在什么时间，而各端再根据它批量更新本地消息和计数。",{"type":83,"tag":101,"props":102,"children":104},"h2",{"id":103},"为什么不只同步一个未读数量",[105],{"type":89,"value":106},"为什么不只同步一个未读数量？",{"type":83,"tag":91,"props":108,"children":109},{},[110],{"type":89,"value":111},"数量描述结果，却没有说明哪些消息已经阅读。不同设备接收和补历史的时间不同，单独同步一个数字，很难与各自的消息集合对应。",{"type":83,"tag":91,"props":113,"children":114},{},[115],{"type":89,"value":116},"同步阅读边界可以让本地重算哪些消息已读，也不必跨端逐条传布尔值。项目仍保留消息级阅读状态供存储和展示，但跨端进度由会话光标关联。",{"type":83,"tag":91,"props":118,"children":119},{},[120],{"type":89,"value":121},"本人读到哪里、对方是否读过我发的消息、消息发送是否成功，也分别保存。它们影响不同界面，不能都塞进一个“成功”状态。",{"type":83,"tag":101,"props":123,"children":125},{"id":124},"旧响应晚到应该覆盖还是忽略",[126],{"type":89,"value":127},"旧响应晚到，应该覆盖还是忽略？",{"type":83,"tag":91,"props":129,"children":130},{},[131],{"type":89,"value":132},"如果允许按到达顺序覆盖，读时间 2000 之后又收到旧值 1000，位置就会倒退。可以用服务端序号或版本确定先后；项目当前选择比较光标中的阅读时间。",{"type":83,"tag":91,"props":134,"children":135},{},[136],{"type":89,"value":137},"新时间更大就接纳，旧时间更小就保留当前值。时间相同时，只在本地缺消息 ID、传入结果有 ID 时补上。当前时间为空、新值有效时可以接纳；新值没有时间，则不覆盖已有有效位置。",{"type":83,"tag":91,"props":139,"children":140},{},[141],{"type":89,"value":142},"这里没有比较两个消息 ID 的字典序，因为 ID 未必代表消息顺序。这套方法也依赖各链路的时间可比较；同时间的精确边界、主动重置进度和更严格顺序，需要另行定义版本或序号。",{"type":83,"tag":101,"props":144,"children":146},{"id":145},"同一个聊天对象为什么还要区分来源群",[147],{"type":89,"value":148},"同一个聊天对象，为什么还要区分来源群？",{"type":83,"tag":91,"props":150,"children":151},{},[152],{"type":89,"value":153},"项目存在群下私聊：一个成员可以在群甲的业务下和工作人员单独沟通，也可以在群乙下再聊。对象是同一人，业务范围却不同。",{"type":83,"tag":91,"props":155,"children":156},{},[157],{"type":89,"value":158},"如果只按对象地址保存光标，读完群甲就可能清掉群乙的未读。项目因此把会话地址与来源群一起用于光标身份，历史请求、存储、分页、通知和计数也沿用同一范围。",{"type":83,"tag":91,"props":160,"children":161},{},[162],{"type":89,"value":163},"没有来源群的主会话，表示只处理消息本身也不带来源群的那部分，而不是不筛选、取出该对象所有消息。Web 部分未读统计已区分这两种含义，其他查询和更新入口仍需保持一致。光标 key 写对了，并不会自动修正漏传范围的调用方。",{"type":83,"tag":101,"props":165,"children":167},{"id":166},"光标合并正确为什么页面还没变",[168],{"type":89,"value":169},"光标合并正确，为什么页面还没变？",{"type":83,"tag":91,"props":171,"children":172},{},[173],{"type":89,"value":174},"页面与数据库可能各有一份状态。只写数据库，当前界面仍可能显示内存中的旧值；只改内存，刷新后又会从数据库恢复旧值。",{"type":83,"tag":91,"props":176,"children":177},{},[178],{"type":89,"value":179},"项目的 Web 处理会批量更新数据库、修改内存消息状态，再刷新未读数。中间一步失败时，仍需要后续同步校准。",{"title":181,"searchDepth":182,"depth":182,"links":183},"",2,[184,185,186,187],{"id":103,"depth":182,"text":106},{"id":124,"depth":182,"text":127},{"id":145,"depth":182,"text":148},{"id":166,"depth":182,"text":169},{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":189},"\u003Cpath fill=\"currentColor\" d=\"m10.828 12l4.95 4.95l-1.414 1.415L8 12l6.364-6.364l1.414 1.414z\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":191},"\u003Cpath fill=\"currentColor\" d=\"M3 4h18v2H3zm0 7h12v2H3zm0 7h18v2H3z\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":193},"\u003Cpath fill=\"currentColor\" d=\"m13.172 12l-4.95-4.95l1.414-1.413L16 12l-6.364 6.364l-1.414-1.415z\"\u002F>"]