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 只在外层 payload;body 仅内容(无 from_id) |
| 消息查询 | HTTP POST /api/v1/message/records 等(非发送);每条 MessageRecord 含同层 from_id、talk_mode、to_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(含 Authorization、X-Content-Encoding、Refresh-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-v1;FormData 上传跳过加密(明文 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 |
否 | 本地库名前缀(tostore 的 dbName),默认 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.gradle 与 gradle-wrapper.properties 覆盖到已安装插件的 Android 目录。再次 flutter pub get 会还原 pub-cache 中的插件文件,需重新执行脚本。
打开链接与商品卡
- Android / iOS / macOS:优先 Deep Link;H5 默认半屏
webview_flutter;openType=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 内部会:
- 检查网络与 WS 连接(
canSend/assertCanSend) - 写入本地库(status=发送中)
- 上行
im.message.send并等待 ack - 成功后更新 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.event 为 im.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_id、talk_mode、to_from_id。滚动升级期间若某条缺少 talk_mode 或 to_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 可发;flashRead 或 openType=browser 时 extra.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 |
好友申请被同意或拒绝,applyResult 为 accept / decline |
|
onFriendOnlineStatusChanged |
好友首个连接上线或最后一个连接下线 | |
onFriendDeleted |
本机、其他设备或对方解除好友关系 | |
OnUserListener |
onSelfInfoUpdated |
本机或其他设备修改本人资料 |
OnGroupListener |
onGroupApplicationAdded |
收到入群申请 |
onGroupMemberChanged |
群成员加入/退出,type 为 1 / 2 |
|
onGroupDismissed |
群被解散(系统消息 1106);SDK 会先删本地会话再回调 |
创建与解散群
createGroup 的 userIds 为其他成员,服务端要求至少 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)后再筛缓存返回;lastCounts 含 stranger(friend 仅好友私聊) |
ContactInfo.isBlacklisted / isOfficialAccount |
is_blacklist == 1 / is_official == 1 |
conversationManager.createConversation |
创建会话(搜索到任意用户即可,无需先 addFriend) |
ContactInfo.canDirectMessage |
非本人(relation≠4)即可直接私聊 |
POST /api/v1/user/setting → user_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=true时content是完整 JSON,并生成MsgType.miniProgram消息。 - 闪阅:小程序卡在
flashRead: true或商品openType=browser时触发;AiChunk/extra含闪阅字段;示例用FlashReadCountdown展示倒计时。SDK:createMiniProgramMessage(..., flashRead: true)。
商品卡片点击(Deep Link → 按 openType 降级)
对话流中商品卡片采用 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_launcher 以 LaunchMode.externalApplication 打开 Deep Link |
| 2. 按 openType 降级 | webview(默认)→ 端内半屏 WebView;browser → 系统浏览器 |
Web 端按 openType 打开 h5Url(browser→新标签,webview→端内抽屉)。示例渠道见 go-demo/README.md(FuFu Card、CardGF、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/unreadOnly;lastCounts含friend/stranger/group/unread(已移除getLocalConversationList/ 冷却 / Future 合并);列表查询显式limit,避免 toStore 默认 1000 行截断
License
Libraries
- enum/login_status
- enum/msg_type
- enum/network_type
- enum/talk_mode
- enum/ws_events
- go_chat
- go_chat_sdk
- GoChat Flutter IM SDK
- listener/on_advanced_msg_listener
- listener/on_conn_listener
- listener/on_conversation_listener
- listener/on_friendship_listener
- listener/on_group_listener
- listener/on_user_listener
- manager/conversation_manager
- manager/friendship_manager
- manager/group_manager
- manager/im_manager
- manager/message_manager
- manager/upload_manager
- manager/user_manager
- models/contact_info
- models/conversation_draft
- models/conversation_info
- models/group_info
- models/message
- models/upload_models
- models/user_info
- models/ws_frame
- network/connectivity_monitor
- network/http_client
- network/media_policy
- network/network_manager
- network/ws_client
- platform/open_http_url
- platform/platform_capabilities
- platform/show_in_app_browser
- platform/show_in_app_browser_io
- platform/show_in_app_browser_stub
- storage/local_storage
- ui/chat_screenshot_guard
- ui/in_app_webview_sheet
- ui/in_app_webview_sheet_io
- ui/in_app_webview_sheet_stub
- ui/link_message
- ui/link_preview_service
- ui/product_link_launcher
- ui/screenshot_guard_backend
- ui/screenshot_guard_backend_io
- ui/screenshot_guard_backend_stub
- utils/aes_envelope
- utils/json_util
- utils/logger