通过之前的《工程目录》可以看到,该工程并不存在request.js之类的进行通信的js文件,所以它是完全通过云信sdk与云信服务器进行信息通信的,只要了解了这个,工程中其它部分都只是个“壳”而已。_

初始化

页面组件

以工程首页“最近消息列表”页面(src/pages/Session.vue)为出发点,找一下它的数据来源于哪。

先贴一下代码:

  1. <template>
  2. <div class="g-inherit m-main p-session">
  3. <group class="u-list">
  4. <cell class="u-list-item" title="消息中心" @click.native="enterSysMsgs">
  5. <img class="icon" slot="icon" width="24" :src="noticeIcon">
  6. <span v-show="sysMsgUnread > 0" class="u-unread">{{sysMsgUnread}}</span>
  7. </cell>
  8. <cell class="u-list-item" title="我的手机" @click.native="enterMyChat">
  9. <img class="icon" slot="icon" width="24" :src="myPhoneIcon">
  10. </cell>
  11. <cell
  12. v-for="(session, index) in sessionlist"
  13. class="u-list-item"
  14. :title="session.name"
  15. :inline-desc="session.lastMsgShow"
  16. :key="session.id"
  17. :sessionId="session.id"
  18. v-touch:swipeleft="showDelBtn"
  19. v-touch:swiperight="hideDelBtn"
  20. @click.native="enterChat(session)" >
  21. <img class="icon u-circle" slot="icon" width="24" :src="session.avatar">
  22. <span class='u-session-time'>
  23. {{session.updateTimeShow}}
  24. </span>
  25. <span v-show="session.unread > 0" class="u-unread">{{session.unread}}</span>
  26. <span
  27. class="u-tag-del"
  28. :class="{active: delSessionId===session.id}"
  29. @click="deleteSession"
  30. ></span>
  31. </cell>
  32. </group>
  33. </div>
  34. </template>
  35. <script>
  36. import util from '../utils'
  37. import config from '../configs'
  38. export default {
  39. data () {
  40. return {
  41. delSessionId: null,
  42. stopBubble: false,
  43. noticeIcon: config.noticeIcon,
  44. myPhoneIcon: config.myPhoneIcon,
  45. myGroupIcon: config.defaultGroupIcon,
  46. myAdvancedIcon: config.defaultAdvancedIcon
  47. }
  48. },
  49. computed: {
  50. sysMsgUnread () {
  51. let temp = this.$store.state.sysMsgUnread
  52. let sysMsgUnread = temp.addFriend || 0
  53. sysMsgUnread += temp.team || 0
  54. let customSysMsgUnread = this.$store.state.customSysMsgUnread
  55. return sysMsgUnread + customSysMsgUnread
  56. },
  57. userInfos () {
  58. return this.$store.state.userInfos
  59. },
  60. myInfo () {
  61. return this.$store.state.myInfo
  62. },
  63. myPhoneId () {
  64. return `${this.$store.state.userUID}`
  65. },
  66. sessionlist () {
  67. let sessionlist = this.$store.state.sessionlist.filter(item => {
  68. item.name = ''
  69. item.avatar = ''
  70. if (item.scene === 'p2p') {
  71. let userInfo = null
  72. if (item.to !== this.myPhoneId) {
  73. userInfo = this.userInfos[item.to]
  74. } else {
  75. // userInfo = this.myInfo
  76. // userInfo.alias = '我的手机'
  77. // userInfo.avatar = `${config.myPhoneIcon}`
  78. return false
  79. }
  80. if (userInfo) {
  81. item.name = util.getFriendAlias(userInfo)
  82. item.avatar = userInfo.avatar
  83. }
  84. } else if (item.scene === 'team') {
  85. let teamInfo = null
  86. teamInfo = this.$store.state.teamlist.find(team => {
  87. return team.teamId === item.to
  88. })
  89. if (teamInfo) {
  90. item.name = teamInfo.name
  91. item.avatar = teamInfo.avatar || (teamInfo.type === 'normal' ? this.myGroupIcon : this.myAdvancedIcon)
  92. } else {
  93. item.name = `群${item.to}`
  94. item.avatar = item.avatar || this.myGroupIcon
  95. }
  96. }
  97. let lastMsg = item.lastMsg || {}
  98. if (lastMsg.type === 'text') {
  99. item.lastMsgShow = lastMsg.text || ''
  100. } else if (lastMsg.type === 'custom') {
  101. item.lastMsgShow = util.parseCustomMsg(lastMsg)
  102. } else if (lastMsg.scene === 'team' && lastMsg.type === 'notification') {
  103. item.lastMsgShow = util.generateTeamSysmMsg(lastMsg)
  104. } else if (util.mapMsgType(lastMsg)) {
  105. item.lastMsgShow = `[${util.mapMsgType(lastMsg)}]`
  106. } else {
  107. item.lastMsgShow = ''
  108. }
  109. if (item.updateTime) {
  110. item.updateTimeShow = util.formatDate(item.updateTime, true)
  111. }
  112. return item
  113. })
  114. return sessionlist
  115. }
  116. },
  117. methods: {
  118. enterSysMsgs () {
  119. if (this.hideDelBtn())
  120. return
  121. location.href = '#/sysmsgs'
  122. },
  123. enterChat (session) {
  124. if (this.hideDelBtn())
  125. return
  126. if (session && session.id)
  127. location.href = `#/chat/${session.id}`
  128. },
  129. enterMyChat () {
  130. // 我的手机页面
  131. location.href = `#/chat/p2p-${this.myPhoneId}`
  132. },
  133. deleteSession () {
  134. if (this.delSessionId !== null) {
  135. this.$store.dispatch('deleteSession', this.delSessionId)
  136. }
  137. },
  138. showDelBtn (vNode) {
  139. if (vNode && vNode.data && vNode.data.attrs) {
  140. this.delSessionId = vNode.data.attrs.sessionId
  141. this.stopBubble = true
  142. setTimeout(() => {
  143. this.stopBubble = false
  144. }, 20)
  145. }
  146. },
  147. hideDelBtn () {
  148. if (this.delSessionId !== null && !this.stopBubble) {
  149. // 用于判断是否前置状态是显示删除按钮
  150. this.delSessionId = null
  151. return true
  152. }
  153. return false
  154. }
  155. }
  156. }
  157. </script>
  158. <style type="text/css">
  159. .p-session {
  160. .vux-cell-primary {
  161. max-width: 70%;
  162. }
  163. }
  164. </style>

该页面组件内容比较多,不需要去看,跟着我的思路走就好。

我们看到第12行v-for="(session, index) in sessionlist"可知数据是通过遍历sessionlist渲染出来的。

那么看下script部分sessionlist是来自哪里,进而发现了它只是computed中的一个function(第68行),也就是说它本身并不负责请求数据!再仔细看functin中的内容的话,会发现她是从$store取出来的值。

存储位置

通过编辑器搜索 sessionlist 可以快速地在 src/store/state.js 找到它的存储位置。既然存储位置找到, 那究竟是谁放进去的呢,再看下搜索结果中 src/store/mutations/index.js 文件。

  1. updateSessions (state, sessions) {
  2. const nim = state.nim
  3. state.sessionlist = nim.mergeSessions(state.sessionlist, sessions)
  4. state.sessionlist.sort((a, b) => {
  5. return b.updateTime - a.updateTime
  6. })
  7. state.sessionlist.forEach(item => {
  8. state.sessionMap[item.id] = item
  9. })
  10. },

可看出它是通过 state.sessionlist = nim.mergeSessions(state.sessionlist, sessions) 对state的sessionlist进行了赋值。

赋值位置

此时我们定位到了updateSessions function,那再搜索它看看,结果在 src/store/actions/session.js 中找到有两个function调用了它。

  1. // onSessions只在初始化完成后回调
  2. export function onSessions (sessions) {
  3. updateSessionAccount(sessions)
  4. store.commit('updateSessions', sessions)
  5. }
  6. export function onUpdateSession (session) {
  7. let sessions = [session]
  8. updateSessionAccount(sessions)
  9. store.commit('updateSessions', sessions)
  10. }

看函数内容可知,它们分别用作初始化和更新。当下最关心的是如何通过初始化得到了数据,于是我们再通过 onSessions 去搜索看看。

连接初始化文件

搜索到了 src/store/actions/initNimSDK.js 使用到了它,下面贴的代码的第30行位置。

  1. /*
  2. * SDK连接相关
  3. */
  4. import config from '@/configs'
  5. import pageUtil from '@/utils/page'
  6. import util from '@/utils'
  7. import store from '../'
  8. import {onFriends, onSyncFriendAction} from './friends'
  9. import {onRobots} from './robots'
  10. import {onBlacklist, onMarkInBlacklist} from './blacks'
  11. import {onMyInfo, onUserInfo} from './userInfo'
  12. import {onSessions, onUpdateSession} from './session'
  13. import {onRoamingMsgs, onOfflineMsgs, onMsg} from './msgs'
  14. import {onSysMsgs, onSysMsg, onSysMsgUnread, onCustomSysMsgs} from './sysMsgs'
  15. import { onTeams, onSynCreateTeam, onCreateTeam, onUpdateTeam, onTeamMembers, onUpdateTeamMember, onAddTeamMembers, onRemoveTeamMembers, onUpdateTeamManagers, onDismissTeam, onUpdateTeamMembersMute, onTeamMsgReceipt} from './team'
  16. const SDK = require('@/sdk/' + config.sdk)
  17. // 重新初始化 NIM SDK
  18. export function initNimSDK ({ state, commit, dispatch }, loginInfo) {
  19. if (state.nim) {
  20. state.nim.disconnect()
  21. }
  22. dispatch('showLoading')
  23. // 初始化SDK
  24. window.nim = state.nim = SDK.NIM.getInstance({
  25. //... 省去上半部分
  26. // 会话
  27. onsessions: onSessions,
  28. onupdatesession: onUpdateSession,
  29. // 消息
  30. onroamingmsgs: onRoamingMsgs,
  31. onofflinemsgs: onOfflineMsgs,
  32. //...省去下半部分
  33. })
  34. window.nim.useDb = config.useDb
  35. }

SDK.NIM.getInstance函数中我省略了上下部分,内容与 onsessions: onSessions 类似,传了很多键值对给到它。

文件的注释写得很清楚,该文件或者说该文件唯一对出口函数 initNimSDK 的作用就是重新初始化 NIM SDK

执行初始化

搜索 initNimSDK 关键字,会发现 src/store/actions/index.js 文件用到了它,该文件也是vuex的actions总出口文件。

那么具体是怎么进行初始化的呢?

先贴代码:

  1. // Action 提交的是 mutation,而不是直接变更状态。
  2. // Action 可以包含任意异步操作。
  3. import cookie from '../../utils/cookie'
  4. import pageUtil from '../../utils/page'
  5. /* 导出actions方法 */
  6. import {showLoading, hideLoading, showFullscreenImg, hideFullscreenImg} from './widgetUi'
  7. import {initNimSDK} from './initNimSDK'
  8. import {initChatroomSDK, resetChatroomSDK} from './initChatroomSDK'
  9. import {updateBlack} from './blacks'
  10. import {updateFriend, addFriend, deleteFriend} from './friends'
  11. import {resetSearchResult, searchUsers, searchTeam} from './search'
  12. import {deleteSession, setCurrSession, resetCurrSession} from './session'
  13. import {sendMsg, sendFileMsg, sendMsgReceipt, sendRobotMsg, revocateMsg, updateLocalMsg, getHistoryMsgs, resetNoMoreHistoryMsgs, continueRobotMsg} from './msgs'
  14. import {markSysMsgRead, resetSysMsgs, deleteSysMsgs, markCustomSysMsgRead} from './sysMsgs'
  15. import {sendChatroomMsg, sendChatroomRobotMsg, sendChatroomFileMsg, getChatroomHistoryMsgs} from './chatroomMsgs'
  16. import {initChatroomInfos, getChatroomInfo, getChatroomMembers, clearChatroomMembers} from './chatroomInfos'
  17. import { delegateTeamFunction, onTeamNotificationMsg, enterSettingPage, getTeamMembers, checkTeamMsgReceipt, getTeamMsgReads} from './team'
  18. function connectNim ({state, commit, dispatch}, obj) {
  19. let {force} = Object.assign({}, obj)
  20. // 操作为内容页刷新页面,此时无nim实例
  21. if (!state.nim || force) {
  22. let loginInfo = {
  23. uid: cookie.readCookie('uid'),
  24. sdktoken: cookie.readCookie('sdktoken'),
  25. }
  26. if (!loginInfo.uid) {
  27. // 无cookie,直接跳转登录页
  28. pageUtil.turnPage('无历史登录记录,请重新登录', 'login')
  29. } else {
  30. // 有cookie,重新登录
  31. dispatch('initNimSDK', loginInfo)
  32. }
  33. }
  34. }
  35. function connectChatroom ({state, commit, dispatch}, obj) {
  36. let {chatroomId} = Object.assign({}, obj)
  37. const nim = state.nim
  38. if (nim) {
  39. dispatch('showLoading')
  40. nim.getChatroomAddress({
  41. chatroomId,
  42. done: function getChatroomAddressDone (error, obj) {
  43. if (error) {
  44. alert(error.message)
  45. location.href = '#/room'
  46. return
  47. }
  48. dispatch('initChatroomSDK', obj)
  49. }
  50. })
  51. }
  52. }
  53. export default {
  54. updateRefreshState ({commit}) {
  55. commit('updateRefreshState')
  56. },
  57. // UI 及页面状态变更
  58. showLoading,
  59. hideLoading,
  60. showFullscreenImg,
  61. hideFullscreenImg,
  62. continueRobotMsg,
  63. // 连接sdk请求,false表示强制重连
  64. connect (store, obj) {
  65. let {type} = Object.assign({}, obj)
  66. // type 可为 nim chatroom
  67. type = type || 'nim'
  68. switch (type) {
  69. case 'nim':
  70. connectNim(store, obj)
  71. break
  72. case 'chatroom':
  73. connectChatroom(store, obj)
  74. break
  75. }
  76. },
  77. // 用户触发的登出逻辑
  78. logout ({ state, commit }) {
  79. cookie.delCookie('uid')
  80. cookie.delCookie('sdktoken')
  81. if (state.nim) {
  82. state.nim.disconnect()
  83. }
  84. pageUtil.turnPage('', 'login')
  85. },
  86. // 初始化 重新连接SDK
  87. initNimSDK,
  88. // 清空所有搜索历史纪录
  89. resetSearchResult,
  90. // 搜索用户信息
  91. searchUsers,
  92. // 更新黑名单
  93. updateBlack,
  94. // 更新好友
  95. addFriend,
  96. deleteFriend,
  97. updateFriend,
  98. // 删除会话
  99. deleteSession,
  100. // 设置当前会话
  101. setCurrSession,
  102. // 重置当前会话
  103. resetCurrSession,
  104. // 发送消息
  105. sendMsg,
  106. sendFileMsg,
  107. sendRobotMsg,
  108. // 发送消息已读回执
  109. sendMsgReceipt,
  110. // 消息撤回
  111. revocateMsg,
  112. // 更新本地消息
  113. updateLocalMsg,
  114. getHistoryMsgs,
  115. // 重置历史消息状态
  116. resetNoMoreHistoryMsgs,
  117. // 标记系统消息已读
  118. markSysMsgRead,
  119. markCustomSysMsgRead,
  120. resetSysMsgs,
  121. deleteSysMsgs,
  122. initChatroomSDK,
  123. initChatroomInfos,
  124. resetChatroomSDK,
  125. sendChatroomMsg,
  126. sendChatroomRobotMsg,
  127. sendChatroomFileMsg,
  128. getChatroomHistoryMsgs,
  129. getChatroomInfo,
  130. getChatroomMembers,
  131. clearChatroomMembers,
  132. // 搜索群
  133. searchTeam,
  134. // 代理sdk中的群方法
  135. delegateTeamFunction,
  136. // 处理群消息回调
  137. onTeamNotificationMsg,
  138. // 进入群信息设置页
  139. enterSettingPage,
  140. // 获取群成员
  141. getTeamMembers,
  142. // 群消息回执检查
  143. checkTeamMsgReceipt,
  144. // 查询群消息回执已读列表
  145. getTeamMsgReads,
  146. }

该代码是完整代码,内容比较长,建议跟着我的思路去看。

我们看到在第8行引入了 initNimSDK.js ,并取出了initNimSDK函数。然后在第95行直接把 initNimSDK 作为actions暴露了出去。

搜索全工程看哪里用到了initNimSDK 这个actions,结果发现只有文件本身的第33行在connectNim 函数中用到了它。

在看文件中只有第76行的 connect actions调用了connectNim 函数,该函数用来创建聊天通信和直播室通信的连接。

再搜索 this.$store.dispatch('connect') 就可以在 src/App.vue 文件的updated生命周期中找到它了。

  1. // 所有页面更新都会触发此函数
  2. updated () {
  3. // 提交sdk连接请求
  4. this.$store.dispatch('connect')
  5. this.$store.dispatch('updateRefreshState')
  6. },

小结

至此,网易云信sdk的初始化与信息通信的建立逻辑就理顺了。

  1. 页面加载后 App.vue 这个入口组件会被创建,updated的生命周期会执行connect actions的调用。
  2. connect actions会经过initNimSDK完成sdk的初始化
  3. 初始化过程中会执行onSessions actions完成对state中sessionlist的赋值。

信息通信

接下来我们看看信息通信是怎么进行的,这个相对比较简单。

看下官方文档,或者 src/pages/components/ChatEditor.vue 关于 sendMsg actions的调用可以轻易定位到 src/store/actions/msgs.js 文件中的sendMsg 函数。

  1. // 发送普通消息
  2. export function sendMsg ({state, commit}, obj) {
  3. const nim = state.nim
  4. obj = obj || {}
  5. let type = obj.type || ''
  6. store.dispatch('showLoading')
  7. switch (type) {
  8. case 'text':
  9. nim.sendText({
  10. scene: obj.scene,
  11. to: obj.to,
  12. text: obj.text,
  13. done: onSendMsgDone,
  14. needMsgReceipt: obj.needMsgReceipt || false
  15. })
  16. break
  17. case 'custom':
  18. nim.sendCustomMsg({
  19. scene: obj.scene,
  20. to: obj.to,
  21. pushContent: obj.pushContent,
  22. content: JSON.stringify(obj.content),
  23. done: onSendMsgDone
  24. })
  25. }
  26. }

该函数内部通过 nim.sendText() 进行信息通信,而这里的nim变量是前面“sdk初始化”时存储进去的。

通过vuex中state的nim变量从而实现了官网所说的“所有业务均通过 NIM SDK 单例调用”。

总结

至此,云信web demo中sdk的初始化与信息通信的逻辑树立告一段落。了解了这一部分,整个工程完全可以脱离他们的demo自己写一套工程出来了。