使用 Flutter 快速实现聊天应用

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"作者:隋晓旭"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"你是否想过从头开发一款类似 QQ、微信的聊天应用?又或者,想要在你开发的应用中加入聊天功能,方便用户交流,增强用户粘性?那么,这篇文章就是为你准备的。在这篇文章中,我将介绍如何基于 Flutter 快速实现一款聊天应用。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"应用简介"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这个基于 Flutter 开发的应用还在持续完善中,现已经支持如下功能:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"登录、登出"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"发起单聊"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"发起群聊"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"支持文字消息、语音消息、图片消息"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"支持展示未读消息数"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"支持展示会话成员"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"支持修改会话名称"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"支持离线消息推送"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"投诉举报不良信息"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"把某用户加入黑名单"}]}]}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"页面截图"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/07/07940e6992c6a1c5301a8be2cee77855.jpeg","alt":"1","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/8f/8fe6e8adee40b09a262418f536c86aef.jpeg","alt":"2","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"开发环境搭建"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第一步:Flutter 安装和环境搭建直接查看:"},{"type":"link","attrs":{"href":"https://flutter.dev/docs/get-started/install","title":""},"content":[{"type":"text","text":" Flutter 文档"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第二步:登录 "},{"type":"link","attrs":{"href":"https://leancloud.cn/dashboard/login.html#/signin","title":""},"content":[{"type":"text","text":"LeanCloud 控制台"}]},{"type":"text","text":",创建 LeanCloud 应用。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在控制台 > 应用 > 设置 >域名绑定页面绑定 "},{"type":"text","marks":[{"type":"strong"}],"text":"API 访问域名"},{"type":"text","text":"。暂时没有域名可以略过这一步,LeanCloud 也提供了短期有效的免费体验域名;或者注册"},{"type":"link","attrs":{"href":"https://console.leancloud.app/login.html#/signin","title":""},"content":[{"type":"text","text":" LeanCloud 国际版"}]},{"type":"text","text":",国际版不要求绑定域名。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在控制台 > 应用 > 设置 > 应用 Keys 页面记录 AppID、AppKey 与服务器地址备用,这里的服务器地址就是 REST API 服务器地址。如果未绑定域名,控制台相同的位置可以获取到免费的共享域名。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"APP 初始化设置"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 pubspec.yaml 中,将 LeanCloud Flutter SDK 添加到依赖项列表:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"dependencies:\n leancloud_official_plugin: ^1.0.0-beta.8 //即时通信插件\n leancloud_storage: ^0.2.9 //数据存储 SDK"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然后运行 flutter pub get 安装 SDK。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因为 leancloud"},{"type":"text","marks":[{"type":"italic"}],"text":"official"},{"type":"text","text":"plugin 是基于 "},{"type":"link","attrs":{"href":"https://github.com/leancloud/swift-sdk","title":""},"content":[{"type":"text","text":"Swift SDK"}]},{"type":"text","text":" 以及 [Java Unified SDK](https://github.com/leancloud/java-unified-sdk) 开发,所以还要安装后面两个 SDK,这样应用才能分别在 iOS 和 Android 设备运行。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"需要通过 CocoaPods 安装 Swift SDK,这一步和安装 iOS 其他第三方库是一样的,在应用的 ios 目录下执行 pod update 即可。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"$ cd ios/\n$ pod update # 或者 $ pod install --repo-update"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"同样的,需要配置 Gradle 来安装 Java Unified SDK,打开工程目录 android/app/build.gradle,添加如下依赖,用 Android Studio 打开工程下的 android 目录,同步更新 Gradle 即可。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"dependencies {\nimplementation 'cn.leancloud:storage-android:6.5.11'\nimplementation 'cn.leancloud:realtime-android:6.5.11'\nimplementation 'io.reactivex.rxjava2:rxandroid:2.1.1'\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"小 tips: 安装 SDK 期间遇到任何困难都可在 "},{"type":"link","attrs":{"href":"https://forum.leancloud.cn/latest","title":""},"content":[{"type":"text","text":"LeanCloud 社区"}]},{"type":"text","text":" 发帖求助。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"SDK 安装成功以后,需要分别 "},{"type":"link","attrs":{"href":"https://leancloud.cn/docs/sdk_setup-flutter.html#hash662673194","title":""},"content":[{"type":"text","text":"初始化 iOS 和 Android 平台"}]},{"type":"text","text":"。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"用户系统"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Demo 里面并没有内置用户系统,可以选择联系人列表中的用户名来登录聊天系统。LeanCloud 即时通信服务端中只需要传入一个唯一标识字符串既可以表示一个「用户」,对应唯一的 Client, 在应用内唯一标识自己的 ID(clientId)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在自己的项目中,如果已经有独立的用户系统也很方便维护。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"或者使用 LeanStorage 提供的"},{"type":"link","attrs":{"href":"https://leancloud.cn/docs/leanstorage_guide-java.html#hash954895","title":""},"content":[{"type":"text","text":"用户系统"}]},{"type":"text","text":"。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"会话列表"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"会话列表要展示当前用户所参与的会话,会话名称、会话的成员,会话的最后一条消息。还需要展示未读消息数目。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"会话列表对应 Conversation 表,查询当前用户的全部会话只需要下面两行代码:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"ConversationQuery query = client.conversationQuery();\nawait query.find();"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"按照会话的更新时间排序:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"query.orderByDescending('updatedAt');"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"为了展示会话的最新一条消息,查询的时候要额外加上这行代码:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"//让查询结果附带一条最新消息\nquery.includeLastMessage = true;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这样会话页面的后端数据就搞定了。下面看一下如何显示数据。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"会话查询成功返回的数据格式是 Conversation 类型的 List。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"conversation.name 即会话的名称"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"conversation.members 即会话成员"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"conversation.lastMessage 就是当前会话的最新一条消息了。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"未读消息数的处理"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果要在 Android 设备上运行,需要在初始化 Java SDK 的时候加上下面这行代码,表示开启未读消息数通知:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"AVIMOptions.getGlobalOptions().setUnreadNotificationEnabled(true);"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"swift SDK 是默认支持,无需额外设置。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以监听 onUnreadMessageCountUpdated 时间获取未读消息数通知:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"client.onUnreadMessageCountUpdated = ({\n Client client,\n Conversation conversation,\n}) {\n // conversation.unreadMessageCount 即该 conversation 的未读消息数量\n};"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"注意要在以下两处清除未读消息数:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在对话列表点击某对话进入到对话页面时"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"用户正在某个对话页面聊天,并在这个对话中收到了消息时"}]}]}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"会话详情页面"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"上拉加载更多历史消息"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"查询聊天记录的时候,先查最近的 10 条消息,然后以第一页的最早的消息作为开始,继续向前拉取消息:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"List messages;\ntry {\n//第一次查询成功\n messages = await conversation.queryMessage(\n limit: 10,\n );\n} catch (e) {\n print(e);\n}\n\ntry {\n // 返回的消息一定是时间增序排列,也就是最早的消息一定是第一个\n Message oldMessage = messages.first;\n // 以第一页的最早的消息作为开始,继续向前拉取消息\n List messages2 = await conversation.queryMessage(\n startTimestamp: oldMessage.sentTimestamp,\n startMessageID: oldMessage.id,\n startClosed: true,\n limit: 10,\n );\n} catch (e) {\n print(e);\n}"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"修改会话名"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"对话的名称是会话表 Conversation 默认的属性,更新会话名称只需要执行:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"await conversation.updateInfo(attributes: {\n 'name': 'New Name',\n});"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"图片、语音消息"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"LeanCloud 即时通信 SDK 提供了下面几种默认的消息类型,Demo 中只用到了文本消息,图像消息和语音消息。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TextMessage 文本消息"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ImageMessage 图像消息"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"AudioMessage 音频消息"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"VideoMessage 视频消息"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"FileMessage 普通文件消息(.txt/.doc/.md 等各种)"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"LocationMessage 地理位置消息"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"注意,图片与语音消息需要先保存成 LCFile,然后再调用发消息接口发消息。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"比如发送音频消息。第一步先保存音频文件为 LCFile:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"LCFile file = await LCFile.fromPath('message.wav', path);\nawait file.save();"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第二步,再发消息:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"//发送消息\nAudioMessage audioMessage = AudioMessage.from(\n binaryData: file.data,\n format: 'wav',\n);\nawait this.widget.conversation.send(message: audioMessage);"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"还要注意 iOS 设备发送图片消息注意打开相册和相机权限,语音消息需要麦克风权限:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"NSMicrophoneUsageDescription\n录音功能需要访问麦克风,如果不允许,你将无法在聊天过程中发送语音消息。\n \nNSCameraUsageDescription\n发送图片功能需要访问您的相机。如果不允许,你将无法在聊天过程中发送图片消息。\n \nNSPhotoLibraryUsageDescription\n发送图片功能需要访问您的相册。如果不允许,你将无法在聊天过程中发送相册中的图片。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"离线推送通知"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"当用户下线以后,收到消息的时候,往往希望能有推送提醒。最简单的一种推送设置就是在 LeanCloud "},{"type":"text","marks":[{"type":"strong"}],"text":"控制台 > 消息 > 即时通讯 > 设置 > 离线推送设置"},{"type":"text","text":" 页面,填入:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"{ \"alert\": \"您有新的消息\", \"badge\": \"Increment\" }"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这样 iOS 设备有离线消息的时候会收到提醒。这里 badge 参数为 iOS 设备专用,用于增加应用 badge 上的数字计数。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果想在 Android 设备上实现离线推送,要增加一步接入"},{"type":"link","attrs":{"href":"https://leancloud.cn/docs/androidmixpushguide.html","title":""},"content":[{"type":"text","text":" Android 混合推送"}]},{"type":"text","text":"。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"当然在实际项目中,离线消息的提醒往往会要求更加具体,比如推送中要包括消息的内容或者消息类型等。LeanCloud 也提供了其他几种定制离线推送的方法,感兴趣可以自行查阅"},{"type":"link","attrs":{"href":"https://leancloud.cn/docs/realtime-guide-intermediate.html#hash-485620600","title":""},"content":[{"type":"text","text":"文档"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"还要注意,iOS 推送一定要正确配置 "},{"type":"link","attrs":{"href":"https://leancloud.cn/docs/iospushcert.html","title":""},"content":[{"type":"text","text":"配置 APNs 推送证书"}]},{"type":"text","text":",并打开 Xcode 的推送开关:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/0d/0d808a8b7322824201e28215b2ee96e3.png","alt":"7","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"AppDelegate.swift 中开启推送,要这样设置:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"import Flutter\nimport LeanCloud\nimport UserNotifications\n\n@UIApplicationMain\n@objc class AppDelegate: FlutterAppDelegate {\n override func application(\n _ application: UIApplication,\n didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?\n ) -> Bool {\n do {\n LCApplication.logLevel = .all\n try LCApplication.default.set(\n id: \"你的APPID\",\n key: \"你的 APPKey\",\n serverURL: \"服务器地址\")\n GeneratedPluginRegistrant.register(with: self)\n /*\n register APNs to access token, like this:\n */\n UNUserNotificationCenter.current().getNotificationSettings { (settings) in\n switch settings.authorizationStatus {\n case .authorized:\n DispatchQueue.main.async {\n UIApplication.shared.registerForRemoteNotifications()\n }\n case .notDetermined:\n UNUserNotificationCenter.current().requestAuthorization(options: [.badge, .alert, .sound]) { (granted, error) in\n if granted {\n DispatchQueue.main.async {\n UIApplication.shared.registerForRemoteNotifications()\n }\n }\n }\n default:\n break\n }\n _ = LCApplication.default.currentInstallation\n }\n return super.application(application, didFinishLaunchingWithOptions: launchOptions)\n } catch {\n fatalError(\"\\(error)\")\n }\n }\n\n override func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {\n /*\n set APNs deviceToken and Team ID.\n */\n LCApplication.default.currentInstallation.set(\n deviceToken: deviceToken,\n apnsTeamId: \"你的 TeamId\")\n /*\n save to LeanCloud.\n */\n LCApplication.default.currentInstallation.save { (result) in\n switch result {\n case .success:\n break\n case .failure(error: let error):\n print(error)\n }\n }\n }\n override func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {\n //如果注册推送失败,可以检查 error 信息\n print(error)\n }\n}"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"投诉举报、黑名单"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 APP Store 审核过程中,因为用户可以随意发送消息,被要求对用户生成的内容要有适当的预防措施。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"要求提供下面这两条内容:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"A mechanism for users to flag objectionable content "}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"用户标记不良信息的机制,就是举报功能。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"A mechanism for users to block abusive users"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"用户阻止滥用用户的机制,实际就是黑名单。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"实现举报功能"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我的解决办法是在消息处长按弹出举报窗口。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/67/673c912f4d871440f930ead8916c09e8.png","alt":"8","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用 LeanCloud 存储服务,新建一张 Report 表用于记录举报信息:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"//保存一条举报信息\nLCObject report = LCObject('Report');\nreport['clientID'] = Global.clientID; //举报人\nreport['messageID'] = messageID; //消息 ID\nreport['conversationID'] = this.widget.conversation.id; //会话 ID\nreport['content'] = _selectedReportList.toString(); //举报内容\nawait report.save(); //保存举报信息"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以在控制台查看举报内容:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/b8/b8712307aa63fb0fe6b5f52458eede8e.png","alt":"9","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"实现黑名单功能"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/8a/8a7daa7e3ec675860f1ab7c3a0bda75c.png","alt":"10","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我的解决办法是,在联系人列表处单击联系人弹框提示是否加入黑名单。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"黑名单实现思路是新建一张 BlackList 表,来保存每个用户的黑名单列表,使用 "},{"type":"link","attrs":{"href":"https://leancloud.cn/docs/realtime-guide-systemconv.html#hash1748033991","title":""},"content":[{"type":"text","text":"LeanCloud 云函数"}]},{"type":"text","text":" 实现屏蔽消息。在 [_messageReceived](https://leancloud.cn/docs/realtime-guide-systemconv.html#hash-1573260517) 这个 Hook 函数下(这个 hook 发生在消息到达 LeanCloud 云端之后),先查此条消息的发件人与收件人是否在黑名单列表,如果在黑名单列表就删除其中要求屏蔽的收件人,返回新的收件人列表。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"实现起来也比较简单,把下面这个云函数粘贴在 LeanCloud 控制台 > 云引擎 >云函数在线编辑框中即可。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/1f/1f76a7b79e7ee200d1cabb3a344309f4.png","alt":"11","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"步骤"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":">"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"先点击「创建函数」,然后选择 _messageReceived 类型,粘贴下面的代码,最后点击「部署」按钮。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"等待部署完成,黑名单功能就已经实现成功,将不再收到加入黑名单用户的全部消息。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"//下面这个函数粘贴在 LeanCloud 控制台\n\nAV.Cloud.define('_messageReceived', async function(request) {\nlet fromPeer = request.params.fromPeer;\nlet toPeersNew = request.params.toPeers;\n\nvar query = new AV.Query('BlackList');\nquery.equalTo('blackedList', fromPeer);\nquery.containedIn('clientID', toPeersNew);\nreturn query.find().then((results) => {\n if (results.length > 0) {\n var clientID = results[0].get('clientID');\n var index = toPeersNew.indexOf(clientID);\n if (index > -1) {\n toPeersNew.splice(index, 1);\n }\n return {\n toPeers: toPeersNew\n }\n }\n return {\n }\n});\n})"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"APP 项目地址"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"APP 项目完整代码见 "},{"type":"link","attrs":{"href":"https://github.com/SXiaoXu/FlutterRealtimeDemo","title":""},"content":[{"type":"text","text":"Github-FlutterRealtimeDemo"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://apps.apple.com/cn/app/leanmessage/id1529417244","title":""},"content":[{"type":"text","text":"APP Store 下载链接"}]}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"文档"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://pub.dev/packages/leancloudofficialplugin#leancloudofficialplugin","title":""},"content":[{"type":"text","text":"LeanCloud 即时通信插件链接"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://leancloud.cn/docs/#即时通讯","title":""},"content":[{"type":"text","text":"LeanCloud 即时通信开发文档"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://flutter.dev/docs","title":""},"content":[{"type":"text","text":"Flutter 文档"}]}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章