go_chat_sdk

中文 | English

修改日志:CHANGELOG.md(English)/ CHANGELOG.zh.md(中文)

面向 go-chat / LumenIM 后端的 Flutter 即时通讯 SDK。
整体架构与 API 风格对齐 open-im-sdk-flutter。 采用 MIT 开源许可;需配合可用的 go-chat / LumenIM 后端才能正常使用。 项目仓库:github.com/whevether/go-chat

目录结构

go_chat_sdk/
├── pubspec.yaml
├── README.md / README.en.md
├── CHANGELOG.md / CHANGELOG.zh.md
├── example/                      # Android / iOS / macOS / Windows / Linux / Web Demo
│   ├── lib/main.dart
│   └── README.md / README.en.md
└── lib/
    ├── go_chat_sdk.dart          # 库导出入口
    ├── go_chat.dart              # GoChat 单例(对位 OpenIM)
    ├── platform/                 # 平台能力与打开链接策略
    ├── manager/
    │   ├── im_manager.dart       # initSDK / login / logout
    │   ├── message_manager.dart  # 消息收发与历史
    │   ├── conversation_manager.dart
    │   ├── user_manager.dart
    │   ├── friendship_manager.dart
    │   └── group_manager.dart
    ├── network/
    │   ├── network_manager.dart  # HTTP + WS + 连通性门面
    │   ├── http_client.dart      # Dio + Bearer 拦截器
    │   ├── ws_client.dart        # 心跳 / 智能重连 / ACK
    │   ├── connectivity_monitor.dart  # connectivity_plus 封装
    │   └── media_policy.dart     # 流量与多媒体策略
    ├── storage/
    │   └── local_storage.dart    # tostore 本地缓存
    ├── listener/                 # OnConnListener 等回调
    ├── models/                   # Message / Conversation / User...
    ├── enum/                     # TalkMode / MsgType / NetworkType / WsEvents
    └── utils/

依赖(固定版本)

能力 版本
WebSocket web_socket_channel ^3.0.3
本地存储 tostore ^3.5.1
Material UI material_ui ^1.1.1
应用目录解析 path_provider ^2.1.5
HTTP dio ^5.10.0
网络监听 connectivity_plus ^7.2.0

connectivity_plus 能力

场景 行为
智能重连 离线暂停退避定时器;网络恢复立即重连,无需空转
瞬时状态 onNetworkChanged / onImConnStatusChanged 驱动顶部状态条
发送即时失败 无网或 WS 未连时立刻抛出 GoChatErrorCode.networkUnavailable,不等 ACK 超时
多媒体策略 Wi‑Fi 原图/自动下发;蜂窝缩略图优先、限制大文件自动下载
listener: OnConnListener(
  onImConnStatusChanged: (s) {
    // networkOffline / connecting / connected / reconnecting
  },
  onNetworkChanged: (type) {},
),

// 发送前
if (!GoChat.iMManager.messageManager.canSend) { /* disable button */ }

// 回前台刷新连通性
await GoChat.iMManager.onAppResumed();

// 上传图片前按策略压缩
final hints = GoChat.iMManager.messageManager.uploadImageHints();
// hints.compress / hints.jpegQuality

与后端协议对齐要点

约定
HTTP POST /api/v1/...,成功直接返回 protobuf JSON(snake_case)
鉴权 Authorization: Bearer <JWT>
WebSocket ws://{host}:{port}/wss/default.io?token=<JWT>
默认端口 本地 go run:HTTP 9501、WS 9502;Docker 映射:9503 / 9504
帧格式 Text JSON:{ "event", "payload" }
发消息 上行 im.message.send,等待 im.message.send.ack / event_error
收消息 下行 im.message:路由三元组 talk_mode / from_id / to_from_id 只在外层 payloadbody 仅内容(无 from_id
消息查询 HTTP POST /api/v1/message/records 等(非发送);每条 MessageRecord 含同层 from_idtalk_modeto_from_id
心跳 客户端定时 {"event":"ping"}
Yes/No 1=否2=是
msg_id 32 位无横线 UUID

平台支持

能力 Android iOS macOS Windows Linux Web
核心 IM(HTTP / WS / AES / Local-First) ✅*
端内 WebView — 外开 — 外开 — 外开
链接 / 商品打开 WebView 或外开 同左 同左 url_launcher url_launcher url_launcher
防截屏 no-op no-op
本地存储 Application Support 同左 同左 同左 同左 IndexedDB

* Web 需后端开启 CORS(含 AuthorizationX-Content-EncodingRefresh-Access-Token),且页面为 HTTPS 时 WebSocket 须用 WSS

无端内 WebView 的平台会通过 url_launcher 打开系统浏览器或新标签,属预期行为。

快速体验(Demo)

cd go_chat_sdk/example
flutter pub get
flutter run -d chrome          # Web
flutter run -d macos           # macOS
flutter run -d windows         # Windows
flutter run -d linux           # Linux
flutter run                    # 默认移动端

真机请把 HTTP/WS 填成电脑局域网 IP;详见 example/README.md / example/README.en.md

快速接入

dependencies:
  go_chat_sdk:
    path: ../go_chat_sdk   # 或你的路径
import 'package:go_chat_sdk/go_chat_sdk.dart';

Future<void> initAndLogin(String accessToken) async {
  // 1. 初始化:动态传入 HTTP / WS 地址
  await GoChat.iMManager.initSDK(
    apiAddr: 'http://127.0.0.1:9501',
    wsAddr: 'ws://127.0.0.1:9502',
    aesKey: '与后端 app.aes_key 一致', // 宿主注入,SDK 不写死
    listener: OnConnListener(
      onConnectSuccess: () {},
      onConnectFailed: (code, err) {},
      onUserTokenExpired: () {},
    ),
  );

  // 2. 设置监听(建议在 login 前)
  GoChat.iMManager
    ..messageManager.setAdvancedMsgListener(OnAdvancedMsgListener(
      onRecvNewMessage: (msg) {},
      onRecvAiChunk: (chunk) {
        // chunk.chunk:当前增量;chunk.content:SDK 累积后的全文
        // chunk.done 后还会触发 onRecvNewMessage
      },
    ))
    ..conversationManager.setConversationListener(OnConversationListener(
      onConversationChanged: (c) {},
      onNewConversation: (c) {},
      onConversationDeleted: (key) {},
    ))
    ..friendshipManager.setFriendshipListener(OnFriendshipListener(
      onFriendApplicationAdded: (apply) {},
      onFriendApplicationResult: (result) {},
      onFriendOnlineStatusChanged: (status) {},
      onFriendDeleted: (userId) {},
    ))
    ..groupManager.setGroupListener(OnGroupListener(
      onGroupApplicationAdded: (apply) {},
      onGroupMemberChanged: (change) {},
      onGroupDismissed: (groupId) {},
    ));

  // 3. Token 登录(内部持久化 Token,并注入 HTTP / WS)
  await GoChat.iMManager.login(token: accessToken);
  // 冷启动可改为:await GoChat.iMManager.loginWithCachedToken();
  // Token 过期 / 401 / 被踢时会清本地缓存,需重新业务登录后再 login。

  // 4. 发消息
  final msg = GoChat.iMManager.messageManager.createTextMessage(
    talkMode: TalkMode.private,
    toFromId: 10086,
    content: 'hello',
  );
  await GoChat.iMManager.messageManager.sendMessage(msg);

  // 5. 拉历史:先读缓存;有网则拉本页增量写库再查库返回
  // 首屏 cursor='0';上拉传当前最老 sequence;私聊打开可 reportRead: true
  final page = await GoChat.iMManager.messageManager.getHistoryMessageList(
    talkMode: TalkMode.private,
    toFromId: 10086,
    cursor: '0',
    reportRead: true,
  );
  // 列表由调用方 merge 渲染;实时消息走 OnAdvancedMsgListener,再调同一入口刷新
}

注意:/api/v1/auth/login 密码在 HTTP AES 信封内以明文传输。业务登录由宿主完成;本 SDK 的 login(token:) 只负责 IM 鉴权与长连接。Demo 的 auth_api.dart / demo_config.dart 演示换 Token 与密钥注入。RSA 仅用于服务端 JWT,不用于密码。

HTTP / WebSocket 整包 AES(始终启用)

与后端 app.aes_key 对齐。HTTP Body 与 WS 每一帧 均为 {iv, data}(AES-CBC-PKCS7,每包随机 IV)。HTTP 请求另带头 X-Content-Encoding: gochat-aes-v1FormData 上传跳过加密(明文 multipart)。密钥仅运行时经 initSDK(aesKey:) 传入(同时用于 HTTP 与 WS),不要写进 SDK 默认值。AI im.message.ai_chunk 仍为逐帧加密,打字机效果不受影响。

文件上传(对齐 Web)

入口:GoChat.iMManager.uploadManager

方法 接口 说明
uploadMediaFile POST /api/v1/upload/media-file 头像 / 图片等,字段 file,可选 width/height,返回 src
uploadEmoticonCustomize POST /api/v1/emoticon/customize/upload 自定义表情,≤5MB
uploadArticleAnnex POST /api/v1/article-annex/upload 笔记附件,字段 annex + article_id,≤10MB
initMultipart POST /api/v1/upload/init-multipart JSON+AES,返回 upload_id / shard_size / shard_num
uploadMultipartShard POST /api/v1/upload/multipart 单分片 FormData,split_index 从 1 起
uploadFileInShards 上述两步封装 串行分片(默认 5MB),返回 uploadId

大文件发消息(对齐 Web):

final uploadId = await GoChat.iMManager.uploadManager.uploadFileInShards(
  bytes: fileBytes,
  fileName: 'doc.pdf',
  onProgress: (p) {},
);
final msg = GoChat.iMManager.messageManager.createFileMessage(
  talkMode: TalkMode.private,
  toFromId: peerId,
  uploadId: uploadId,
);
await GoChat.iMManager.messageManager.sendMessage(msg);

initSDK 参数

参数 类型 必填 说明
apiAddr String HTTP Base URL,如 http://192.168.1.8:9501
wsAddr String WebSocket Base URL,如 ws://192.168.1.8:9502
aesKey String 与后端 app.aes_key 一致(HTTP + WebSocket)
dataDir String 本地库名前缀(tostoredbName),默认 go_chat_sdk
dbPath String? 数据库文件目录;Android/iOS 不传时自动使用应用 Application Support 目录
listener OnConnListener? 连接与网络状态回调
enableLog bool 是否输出调试日志,默认 true

聊天页禁止截屏 / 录屏

SDK 提供 ChatScreenshotGuard:进入子树时开启保护,离开 dispose 时恢复。

return ChatScreenshotGuard(
  child: Scaffold(/* 聊天 UI */),
);

Example 的 ChatPage 已接入。宿主自建聊天页时同样包一层即可。

平台 说明
Android / iOS / macOS / Windows no_screenshot;Android 接近 FLAG_SECURE
Linux / Web no-op(系统能力不足),仅打日志
共性 无法防外置相机;调用失败不阻断聊天

Android 若仍使用较旧的 no_screenshot 1.x 且与 AGP / Kotlin 冲突,可在 flutter pub get 后执行(2.0.1+ 通常无需):

./no_screenshot_android_config/apply.sh
# 或显式指定插件路径
./no_screenshot_android_config/apply.sh ~/.pub-cache/hosted/pub.flutter-io.cn/no_screenshot-1.2.0

该脚本会把 no_screenshot_android_config/ 下的 build.gradlegradle-wrapper.properties 覆盖到已安装插件的 Android 目录。再次 flutter pub get 会还原 pub-cache 中的插件文件,需重新执行脚本。

打开链接与商品卡

  • Android / iOS / macOS:优先 Deep Link;H5 默认半屏 webview_flutteropenType=browser 或失败时外开。
  • Windows / Linux / Web:优先 Deep Link(Web 上通常不可用);H5 一律 url_launcher 外开。
  • 统一入口:ProductLinkLauncher.open / openHttpUrl / openLinkInApp

本地存储(dbPath)

SDK 内部使用 tostore 做本地持久化,包含 access_token、消息缓存、会话草稿/未读等数据。

  • Android / iOS / 桌面:默认 Application Support 目录,多数场景无需传 dbPath
  • Web:使用 IndexedDB,dbPath 保持 null
  • 多用户隔离可传不同 dataDir(库名)。

dataDir 是数据库名称dbPath 是数据库目录,二者不要混淆。

import 'package:path_provider/path_provider.dart';

// 多用户:按 userId 隔离库名
await GoChat.iMManager.initSDK(
  apiAddr: api,
  wsAddr: ws,
  dataDir: 'go_chat_uid_$userId',
);

// 自定义目录(可选)
final dir = await getApplicationSupportDirectory();
await GoChat.iMManager.initSDK(
  apiAddr: api,
  wsAddr: ws,
  dataDir: 'go_chat_demo',
  dbPath: '${dir.path}/go_chat',
);

常见问题

现象 原因 处理
dbPath is required 旧版 SDK 未传移动端目录 升级到当前版本,或手动传 dbPath
登录后无法连 WS 地址或端口填错 核对 HTTP/WS 端口;Docker 用 9503/9504
真机连不上 未在同一局域网 使用电脑局域网 IP,不要用 localhost

发送消息(WebSocket)

final msg = GoChat.iMManager.messageManager.createTextMessage(
  talkMode: TalkMode.private,
  toFromId: 10086,
  content: 'hello',
);
await GoChat.iMManager.messageManager.sendMessage(msg);

SDK 内部会:

  1. 检查网络与 WS 连接(canSend / assertCanSend
  2. 写入本地库(status=发送中)
  3. 上行 im.message.send 并等待 ack
  4. 成功后更新 status=成功,并刷新会话列表

上行帧格式

{
  "event": "im.message.send",
  "payload": {
    "type": "text",
    "talk_mode": 1,
    "to_from_id": 10086,
    "msg_id": "32位无横线UUID",
    "body": { "content": "hello" }
  }
}

响应

event 含义
im.message.send.ack 发送成功,payload.msg_id 与上行一致
event_error 发送失败,payload.eventim.message.send,含 error_code / error_message

鉴权 Token 在 login() 后由 SDK 注入 WS 连接 URL,无需在 payload 中传 Token。完整字段说明见 go-chat/README.md

下行 im.message

路由三元组只在外层;body 不含 from_id。SDK Message.fromPushPayload 只读外层,忽略 body 里可能残留的 from_id

{
  "event": "im.message",
  "payload": {
    "talk_mode": 1,
    "from_id": 10086,
    "to_from_id": 10001,
    "body": {
      "msg_id": "32位无横线UUID",
      "sequence": "12",
      "msg_type": 1,
      "nickname": "Alice",
      "avatar": "https://...",
      "is_revoked": 1,
      "send_time": "2026-09-14 15:00:00",
      "extra": { "content": "hello" },
      "quote": null
    }
  }
}

HTTP POST /api/v1/message/records(及 history-records / IMServer records)每条记录同样带 from_idtalk_modeto_from_id。滚动升级期间若某条缺少 talk_modeto_from_id==0,SDK 会回退到本次请求参数。

消息类型(MsgType

与后端 go-chat/internal/entity/talk.go / lib/enum/msg_type.dart 一致。

常量 WS send type 说明
text 1 text 文本
code 2 code 代码
image 3 image 图片
voice 4 voice 语音
video 5 video 视频
file 6 file 文件
location 7 location 位置
card 8 card 名片
forward 9 forward 转发
login 10 服务端登录欢迎
vote 11 群投票(HTTP 创建)
mixed 12 mixed 图文
groupNotice 13 群公告
miniProgram 14 miniprogram 小程序 / AI 工具卡片;createMiniProgramMessage 可发;flashReadopenType=browserextra.flash_read=true,预览为 [闪阅消息],焚毁后为 [已焚毁]
emoticon -1 emoticon 仅发送用;落库为 image(3)
sysText 1000 系统文本(建群 / 入退踢 / 禁言 / 转让等,展示 extra.content
sysGroupDismissed 1106 群解散;客户端据此删会话

可发送类型与 body 字段以服务端 消息发送 为准。

消息与业务事件回调

Listener 回调 触发时机
OnAdvancedMsgListener onRecvNewMessage 普通新消息;AI 流在 done=true 完成落库后也会触发
onRecvMessageRevoked 消息撤回并更新本地消息状态(闪阅备注多为「闪阅消息已焚毁」)
onRecvMessageRead 私聊对端已读水位(im.message.read);用本端副本 msg_id / sequence
onRecvAiChunk 每个 AI 流式分片;content 是累积全文,可直接实现打字机效果
onRecvTyping 对方输入状态
onMsgSendProgress / onMsgSendFailed 发送进度与失败
OnConnListener onKickedOffline 同账号在其他设备登录;SDK 停止自动重连并退出登录态
onUserTokenExpired HTTP/WS 鉴权失效
OnConversationListener onNewConversation 首条消息创建会话或主动创建会话
onConversationChanged 已有会话消息、未读、草稿等发生变化
onConversationDeleted 本机或其他设备删除会话
OnFriendshipListener onFriendApplicationAdded 收到好友申请
onFriendApplicationResult 好友申请被同意或拒绝,applyResultaccept / decline
onFriendOnlineStatusChanged 好友首个连接上线或最后一个连接下线
onFriendDeleted 本机、其他设备或对方解除好友关系
OnUserListener onSelfInfoUpdated 本机或其他设备修改本人资料
OnGroupListener onGroupApplicationAdded 收到入群申请
onGroupMemberChanged 群成员加入/退出,type1 / 2
onGroupDismissed 群被解散(系统消息 1106);SDK 会先删本地会话再回调

创建与解散群

createGroupuserIds 为其他成员,服务端要求至少 2 人(不含自己)。getGroupDetail().isManager 为当前用户是否群主。解散成功后 SDK 会删本地会话;成员侧靠 1106 触发 onGroupDismissed

final gid = await GoChat.iMManager.groupManager.createGroup(
  name: '周末局',
  userIds: [1001, 1002],
);
await GoChat.iMManager.conversationManager.createConversation(
  talkMode: TalkMode.group,
  toFromId: gid,
);

final detail = await GoChat.iMManager.groupManager.getGroupDetail(gid);
if (detail.isManager) {
  await GoChat.iMManager.groupManager.dismissGroup(gid);
}

示例见 example/lib/pages/create_group_page.dart 与聊天页解散菜单。

私聊已读回执

仅私聊。打开会话时 SDK 会对最新本地消息调用 markMessagesAsRead(上报自己 inbox 的 msg_id),并用 fetchPeerReadState 回填对端水位。markConversationAsRead 只清会话角标,不是已读回执。

自己发出的消息:当 sequence <= messageManager.peerReadSeq(...) 时可显示「已读」。群聊不做已读。

GoChat.iMManager.messageManager.setAdvancedMsgListener(
  OnAdvancedMsgListener(
    onRecvMessageRead: (read) {
      // read.msgId / sequence 是本端副本
    },
  ),
);

会话草稿(仅本机)

草稿只写本地 draft_text不上传、不同步。纯文本原样存储;富文本为 {"text","ops"},读取时兼容旧纯文本。

await GoChat.iMManager.conversationManager.setConversationDraft(
  talkMode: TalkMode.private,
  toFromId: peerId,
  draftText: '未发送的文字',
  ops: quillOps, // 可选
);
final draft = await GoChat.iMManager.conversationManager.getConversationDraft(
  talkMode: TalkMode.private,
  toFromId: peerId,
);

机器人账号与联系人

字段 / API 说明
users.is_robot 1 = 机器人,2 = 普通用户(与 YesNo.yes / YesNo.no 一致)
users.is_official 1 = 官方人员,2 = 否;官方双向禁止拉黑与加好友
friendshipManager.searchContactByMobile POST /api/v1/contact/search
friendshipManager.getContactDetail / resolveContactByMobile 先 search 再 POST /api/v1/contact/detail(含 is_blacklist / is_official
friendshipManager.addBlacklist / removeBlacklist / getBlacklist 自助拉黑(陌生人/机器人可拉黑;官方双向禁止)
ConversationInfo.isFriend / isStranger 私聊是否在通讯录;isStranger = 私聊且非好友
conversationManager.getAllConversationList 先读缓存;有网带 talk_mode / is_friend / unread_only 拉 session-list 增量 upsert(无筛选时 prune)后再筛缓存返回;lastCountsstrangerfriend 仅好友私聊)
ContactInfo.isBlacklisted / isOfficialAccount is_blacklist == 1 / is_official == 1
conversationManager.createConversation 创建会话(搜索到任意用户即可,无需先 addFriend
ContactInfo.canDirectMessage 非本人(relation≠4)即可直接私聊
POST /api/v1/user/settinguser_info.is_robot / is_official 当前登录身份;官方账号应隐藏拉黑入口
await GoChat.iMManager.friendshipManager.addBlacklist(userId);
await GoChat.iMManager.friendshipManager.removeBlacklist(userId);
final list = await GoChat.iMManager.friendshipManager.getBlacklist();
final detail = await GoChat.iMManager.friendshipManager.getContactDetail(userId);
if (detail.isBlacklisted) { /* ... */ }

发消息仍走 messageManager.sendMessage;是否触发 AI 由对方是否为机器人决定。示例 App「咨询助手」直接打开默认账号 13812345678 的资料(无需搜索),点按钮进入会话。机器人账号也可人工回复用户(服务端会同步双方会话);见 go-chat/README.md「机器人账号」。

AI 流式消息与打字机效果

仅当私聊对象是服务端标记的机器人用户users.is_robot = 1,且服务端 robot 表存在对应配置)时,发送文本才会收到 ai_chunk;对普通好友私聊不会触发 AI。

SDK 会按 msgId 累积普通文本分片:

GoChat.iMManager.messageManager.setAdvancedMsgListener(
  OnAdvancedMsgListener(
    onRecvAiChunk: (chunk) {
      // 用相同 msgId 的临时气泡显示 chunk.content
      updateStreamingBubble(chunk.msgId, chunk.content);
    },
    onRecvNewMessage: (msg) {
      // AI done 后与普通新消息完全相同:
      // 已写本地消息库、更新会话与未读,并回调到这里。
      replaceStreamingBubble(msg.msgId, msg);
    },
  ),
);
  • 文字流:chunk 是当前增量,content 是已累积全文,适合逐帧刷新打字机气泡。
  • 工具流:中间空分片可显示骨架屏;done=truecontent 是完整 JSON,并生成 MsgType.miniProgram 消息。
  • 闪阅:小程序卡在 flashRead: true 或商品 openType=browser 时触发;AiChunk / extra 含闪阅字段;示例用 FlashReadCountdown 展示倒计时。SDK:createMiniProgramMessage(..., flashRead: true)

对话流中商品卡片采用 Server-Driven UI:服务端下发 JSON(含 h5Url / deepLink / openType),客户端按模板绘制原生卡片。点击路由封装在 SDK:

await ProductLinkLauncher.open(
  context: context,
  deepLink: product.deepLink, // 如 fufucard://product/123
  h5Url: product.h5Url,       // 如 https://m.fufucard.com/
  openType: product.openType, // webview | browser
);
步骤 行为
1. 优先唤起 url_launcherLaunchMode.externalApplication 打开 Deep Link
2. 按 openType 降级 webview(默认)→ 端内半屏 WebView;browser → 系统浏览器

Web 端按 openType 打开 h5Urlbrowser→新标签,webview→端内抽屉)。示例渠道见 go-demo/README.mdFuFu CardCardGF、MindQQ)。

文本消息链接预览

含 URL 的文本可用 SDK 的 LinkAwareTextMessage:通过 metalink 提取 OG 元数据展示预览卡,点击以半屏 WebView 打开。

LinkAwareTextMessage(
  text: message.previewText,
  isMine: mine,
  previewCache: cache[message.msgId],
  onPreviewFetched: (meta) => cache[message.msgId] = meta,
);
  • done=true 后 SDK 会写入本地消息库、更新会话列表,再触发 onRecvNewMessage;业务应按 msgId 替换临时气泡,避免重复显示。
  • 示例实现见 example/lib/pages/chat_page.dart

WebSocket 下行事件

event SDK 行为
im.message 从外层读 from_id/to_from_id/talk_mode;新消息落库、会话更新、触发 onRecvNewMessage;群解散 1106 不入库,改走 onGroupDismissed
im.message.read 私聊已读水位,触发 onRecvMessageRead
im.message.revoke 标记本地消息撤回
im.message.ai_chunk 累积流式内容;完成后按新消息处理
im.user.kicked 停止重连并触发 onKickedOffline
im.user.updated 更新本人缓存并触发资料回调
im.contact.status 好友在线状态变更
im.contact.apply 新好友申请
im.contact.apply_result 好友申请处理结果
im.contact.deleted 好友关系删除
im.conversation.deleted 跨设备会话删除
im.group.apply 入群申请
im.group.member_changed 群成员加入/退出

Manager 对照表(OpenIM → GoChat)

OpenIM GoChat
OpenIM.iMManager GoChat.iMManager
messageManager messageManager
conversationManager conversationManager
userManager userManager
friendshipManager friendshipManager
groupManager groupManager
OnConnectListener OnConnListener
OnAdvancedMsgListener OnAdvancedMsgListener

离线能力 / Local-First

  • Token / 用户信息 KV 持久化
  • 消息 upsert(唯一键 msg_id + owner_id)+ 整数 sequence 分页;按 conversation_key 查询,带 limit
  • 会话完整行 upsert(唯一键 conversation_key + owner_id);草稿 / 未读 / 撤回用部分字段 update;写入检查 hasErrors
  • 统一入口 getHistoryMessageList(先缓存 → 有网增量同步 → 再返回缓存)
  • 会话未读数、草稿本地读写(草稿仅本机,不跨端)
  • getHistoryMessageList:首屏 cursor='0',上拉用最老 sequence;私聊打开可 reportRead: true(无 fromLocal
  • resendPendingMessages:按本地 sending/failed 重试
  • getAllConversationList:先缓存 → 有网带筛选参数拉 session-list 增量 upsert(无筛选时 prune)→ 筛缓存返回;可选 talkMode / isFriend / unreadOnlylastCountsfriend/stranger/group/unread(已移除 getLocalConversationList / 冷却 / Future 合并);列表查询显式 limit,避免 toStore 默认 1000 行截断

License

MIT LicenseOSI 认可)。全文见 LICENSE