通过之前的《工程目录》可以看到,该工程并不存在request.js之类的进行通信的js文件,所以它是完全通过云信sdk与云信服务器进行信息通信的,只要了解了这个,工程中其它部分都只是个“壳”而已。_
初始化
页面组件
以工程首页“最近消息列表”页面(src/pages/Session.vue)为出发点,找一下它的数据来源于哪。
先贴一下代码:
<template><div class="g-inherit m-main p-session"><group class="u-list"><cell class="u-list-item" title="消息中心" @click.native="enterSysMsgs"><img class="icon" slot="icon" width="24" :src="noticeIcon"><span v-show="sysMsgUnread > 0" class="u-unread">{{sysMsgUnread}}</span></cell><cell class="u-list-item" title="我的手机" @click.native="enterMyChat"><img class="icon" slot="icon" width="24" :src="myPhoneIcon"></cell><cellv-for="(session, index) in sessionlist"class="u-list-item":title="session.name":inline-desc="session.lastMsgShow":key="session.id":sessionId="session.id"v-touch:swipeleft="showDelBtn"v-touch:swiperight="hideDelBtn"@click.native="enterChat(session)" ><img class="icon u-circle" slot="icon" width="24" :src="session.avatar"><span class='u-session-time'>{{session.updateTimeShow}}</span><span v-show="session.unread > 0" class="u-unread">{{session.unread}}</span><spanclass="u-tag-del":class="{active: delSessionId===session.id}"@click="deleteSession"></span></cell></group></div></template><script>import util from '../utils'import config from '../configs'export default {data () {return {delSessionId: null,stopBubble: false,noticeIcon: config.noticeIcon,myPhoneIcon: config.myPhoneIcon,myGroupIcon: config.defaultGroupIcon,myAdvancedIcon: config.defaultAdvancedIcon}},computed: {sysMsgUnread () {let temp = this.$store.state.sysMsgUnreadlet sysMsgUnread = temp.addFriend || 0sysMsgUnread += temp.team || 0let customSysMsgUnread = this.$store.state.customSysMsgUnreadreturn sysMsgUnread + customSysMsgUnread},userInfos () {return this.$store.state.userInfos},myInfo () {return this.$store.state.myInfo},myPhoneId () {return `${this.$store.state.userUID}`},sessionlist () {let sessionlist = this.$store.state.sessionlist.filter(item => {item.name = ''item.avatar = ''if (item.scene === 'p2p') {let userInfo = nullif (item.to !== this.myPhoneId) {userInfo = this.userInfos[item.to]} else {// userInfo = this.myInfo// userInfo.alias = '我的手机'// userInfo.avatar = `${config.myPhoneIcon}`return false}if (userInfo) {item.name = util.getFriendAlias(userInfo)item.avatar = userInfo.avatar}} else if (item.scene === 'team') {let teamInfo = nullteamInfo = this.$store.state.teamlist.find(team => {return team.teamId === item.to})if (teamInfo) {item.name = teamInfo.nameitem.avatar = teamInfo.avatar || (teamInfo.type === 'normal' ? this.myGroupIcon : this.myAdvancedIcon)} else {item.name = `群${item.to}`item.avatar = item.avatar || this.myGroupIcon}}let lastMsg = item.lastMsg || {}if (lastMsg.type === 'text') {item.lastMsgShow = lastMsg.text || ''} else if (lastMsg.type === 'custom') {item.lastMsgShow = util.parseCustomMsg(lastMsg)} else if (lastMsg.scene === 'team' && lastMsg.type === 'notification') {item.lastMsgShow = util.generateTeamSysmMsg(lastMsg)} else if (util.mapMsgType(lastMsg)) {item.lastMsgShow = `[${util.mapMsgType(lastMsg)}]`} else {item.lastMsgShow = ''}if (item.updateTime) {item.updateTimeShow = util.formatDate(item.updateTime, true)}return item})return sessionlist}},methods: {enterSysMsgs () {if (this.hideDelBtn())returnlocation.href = '#/sysmsgs'},enterChat (session) {if (this.hideDelBtn())returnif (session && session.id)location.href = `#/chat/${session.id}`},enterMyChat () {// 我的手机页面location.href = `#/chat/p2p-${this.myPhoneId}`},deleteSession () {if (this.delSessionId !== null) {this.$store.dispatch('deleteSession', this.delSessionId)}},showDelBtn (vNode) {if (vNode && vNode.data && vNode.data.attrs) {this.delSessionId = vNode.data.attrs.sessionIdthis.stopBubble = truesetTimeout(() => {this.stopBubble = false}, 20)}},hideDelBtn () {if (this.delSessionId !== null && !this.stopBubble) {// 用于判断是否前置状态是显示删除按钮this.delSessionId = nullreturn true}return false}}}</script><style type="text/css">.p-session {.vux-cell-primary {max-width: 70%;}}</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 文件。
updateSessions (state, sessions) {const nim = state.nimstate.sessionlist = nim.mergeSessions(state.sessionlist, sessions)state.sessionlist.sort((a, b) => {return b.updateTime - a.updateTime})state.sessionlist.forEach(item => {state.sessionMap[item.id] = item})},
可看出它是通过 state.sessionlist = nim.mergeSessions(state.sessionlist, sessions) 对state的sessionlist进行了赋值。
赋值位置
此时我们定位到了updateSessions function,那再搜索它看看,结果在 src/store/actions/session.js 中找到有两个function调用了它。
// onSessions只在初始化完成后回调export function onSessions (sessions) {updateSessionAccount(sessions)store.commit('updateSessions', sessions)}export function onUpdateSession (session) {let sessions = [session]updateSessionAccount(sessions)store.commit('updateSessions', sessions)}
看函数内容可知,它们分别用作初始化和更新。当下最关心的是如何通过初始化得到了数据,于是我们再通过 onSessions 去搜索看看。
连接初始化文件
搜索到了 src/store/actions/initNimSDK.js 使用到了它,下面贴的代码的第30行位置。
/** SDK连接相关*/import config from '@/configs'import pageUtil from '@/utils/page'import util from '@/utils'import store from '../'import {onFriends, onSyncFriendAction} from './friends'import {onRobots} from './robots'import {onBlacklist, onMarkInBlacklist} from './blacks'import {onMyInfo, onUserInfo} from './userInfo'import {onSessions, onUpdateSession} from './session'import {onRoamingMsgs, onOfflineMsgs, onMsg} from './msgs'import {onSysMsgs, onSysMsg, onSysMsgUnread, onCustomSysMsgs} from './sysMsgs'import { onTeams, onSynCreateTeam, onCreateTeam, onUpdateTeam, onTeamMembers, onUpdateTeamMember, onAddTeamMembers, onRemoveTeamMembers, onUpdateTeamManagers, onDismissTeam, onUpdateTeamMembersMute, onTeamMsgReceipt} from './team'const SDK = require('@/sdk/' + config.sdk)// 重新初始化 NIM SDKexport function initNimSDK ({ state, commit, dispatch }, loginInfo) {if (state.nim) {state.nim.disconnect()}dispatch('showLoading')// 初始化SDKwindow.nim = state.nim = SDK.NIM.getInstance({//... 省去上半部分// 会话onsessions: onSessions,onupdatesession: onUpdateSession,// 消息onroamingmsgs: onRoamingMsgs,onofflinemsgs: onOfflineMsgs,//...省去下半部分})window.nim.useDb = config.useDb}
SDK.NIM.getInstance函数中我省略了上下部分,内容与
onsessions: onSessions类似,传了很多键值对给到它。
文件的注释写得很清楚,该文件或者说该文件唯一对出口函数 initNimSDK 的作用就是重新初始化 NIM SDK。
执行初始化
搜索 initNimSDK 关键字,会发现 src/store/actions/index.js 文件用到了它,该文件也是vuex的actions总出口文件。
那么具体是怎么进行初始化的呢?
先贴代码:
// Action 提交的是 mutation,而不是直接变更状态。// Action 可以包含任意异步操作。import cookie from '../../utils/cookie'import pageUtil from '../../utils/page'/* 导出actions方法 */import {showLoading, hideLoading, showFullscreenImg, hideFullscreenImg} from './widgetUi'import {initNimSDK} from './initNimSDK'import {initChatroomSDK, resetChatroomSDK} from './initChatroomSDK'import {updateBlack} from './blacks'import {updateFriend, addFriend, deleteFriend} from './friends'import {resetSearchResult, searchUsers, searchTeam} from './search'import {deleteSession, setCurrSession, resetCurrSession} from './session'import {sendMsg, sendFileMsg, sendMsgReceipt, sendRobotMsg, revocateMsg, updateLocalMsg, getHistoryMsgs, resetNoMoreHistoryMsgs, continueRobotMsg} from './msgs'import {markSysMsgRead, resetSysMsgs, deleteSysMsgs, markCustomSysMsgRead} from './sysMsgs'import {sendChatroomMsg, sendChatroomRobotMsg, sendChatroomFileMsg, getChatroomHistoryMsgs} from './chatroomMsgs'import {initChatroomInfos, getChatroomInfo, getChatroomMembers, clearChatroomMembers} from './chatroomInfos'import { delegateTeamFunction, onTeamNotificationMsg, enterSettingPage, getTeamMembers, checkTeamMsgReceipt, getTeamMsgReads} from './team'function connectNim ({state, commit, dispatch}, obj) {let {force} = Object.assign({}, obj)// 操作为内容页刷新页面,此时无nim实例if (!state.nim || force) {let loginInfo = {uid: cookie.readCookie('uid'),sdktoken: cookie.readCookie('sdktoken'),}if (!loginInfo.uid) {// 无cookie,直接跳转登录页pageUtil.turnPage('无历史登录记录,请重新登录', 'login')} else {// 有cookie,重新登录dispatch('initNimSDK', loginInfo)}}}function connectChatroom ({state, commit, dispatch}, obj) {let {chatroomId} = Object.assign({}, obj)const nim = state.nimif (nim) {dispatch('showLoading')nim.getChatroomAddress({chatroomId,done: function getChatroomAddressDone (error, obj) {if (error) {alert(error.message)location.href = '#/room'return}dispatch('initChatroomSDK', obj)}})}}export default {updateRefreshState ({commit}) {commit('updateRefreshState')},// UI 及页面状态变更showLoading,hideLoading,showFullscreenImg,hideFullscreenImg,continueRobotMsg,// 连接sdk请求,false表示强制重连connect (store, obj) {let {type} = Object.assign({}, obj)// type 可为 nim chatroomtype = type || 'nim'switch (type) {case 'nim':connectNim(store, obj)breakcase 'chatroom':connectChatroom(store, obj)break}},// 用户触发的登出逻辑logout ({ state, commit }) {cookie.delCookie('uid')cookie.delCookie('sdktoken')if (state.nim) {state.nim.disconnect()}pageUtil.turnPage('', 'login')},// 初始化 重新连接SDKinitNimSDK,// 清空所有搜索历史纪录resetSearchResult,// 搜索用户信息searchUsers,// 更新黑名单updateBlack,// 更新好友addFriend,deleteFriend,updateFriend,// 删除会话deleteSession,// 设置当前会话setCurrSession,// 重置当前会话resetCurrSession,// 发送消息sendMsg,sendFileMsg,sendRobotMsg,// 发送消息已读回执sendMsgReceipt,// 消息撤回revocateMsg,// 更新本地消息updateLocalMsg,getHistoryMsgs,// 重置历史消息状态resetNoMoreHistoryMsgs,// 标记系统消息已读markSysMsgRead,markCustomSysMsgRead,resetSysMsgs,deleteSysMsgs,initChatroomSDK,initChatroomInfos,resetChatroomSDK,sendChatroomMsg,sendChatroomRobotMsg,sendChatroomFileMsg,getChatroomHistoryMsgs,getChatroomInfo,getChatroomMembers,clearChatroomMembers,// 搜索群searchTeam,// 代理sdk中的群方法delegateTeamFunction,// 处理群消息回调onTeamNotificationMsg,// 进入群信息设置页enterSettingPage,// 获取群成员getTeamMembers,// 群消息回执检查checkTeamMsgReceipt,// 查询群消息回执已读列表getTeamMsgReads,}
该代码是完整代码,内容比较长,建议跟着我的思路去看。
我们看到在第8行引入了 initNimSDK.js ,并取出了initNimSDK函数。然后在第95行直接把 initNimSDK 作为actions暴露了出去。
搜索全工程看哪里用到了initNimSDK 这个actions,结果发现只有文件本身的第33行在connectNim 函数中用到了它。
在看文件中只有第76行的 connect actions调用了connectNim 函数,该函数用来创建聊天通信和直播室通信的连接。
再搜索 this.$store.dispatch('connect') 就可以在 src/App.vue 文件的updated生命周期中找到它了。
// 所有页面更新都会触发此函数updated () {// 提交sdk连接请求this.$store.dispatch('connect')this.$store.dispatch('updateRefreshState')},
小结
至此,网易云信sdk的初始化与信息通信的建立逻辑就理顺了。
- 页面加载后
App.vue这个入口组件会被创建,updated的生命周期会执行connectactions的调用。 connectactions会经过initNimSDK完成sdk的初始化- 初始化过程中会执行
onSessionsactions完成对state中sessionlist的赋值。
信息通信
接下来我们看看信息通信是怎么进行的,这个相对比较简单。
看下官方文档,或者 src/pages/components/ChatEditor.vue 关于 sendMsg actions的调用可以轻易定位到 src/store/actions/msgs.js 文件中的sendMsg 函数。
// 发送普通消息export function sendMsg ({state, commit}, obj) {const nim = state.nimobj = obj || {}let type = obj.type || ''store.dispatch('showLoading')switch (type) {case 'text':nim.sendText({scene: obj.scene,to: obj.to,text: obj.text,done: onSendMsgDone,needMsgReceipt: obj.needMsgReceipt || false})breakcase 'custom':nim.sendCustomMsg({scene: obj.scene,to: obj.to,pushContent: obj.pushContent,content: JSON.stringify(obj.content),done: onSendMsgDone})}}
该函数内部通过 nim.sendText() 进行信息通信,而这里的nim变量是前面“sdk初始化”时存储进去的。
通过vuex中state的nim变量从而实现了官网所说的“所有业务均通过 NIM SDK 单例调用”。
总结
至此,云信web demo中sdk的初始化与信息通信的逻辑树立告一段落。了解了这一部分,整个工程完全可以脱离他们的demo自己写一套工程出来了。
