通过之前的《工程目录》可以看到,该工程并不存在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>
<cell
v-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>
<span
class="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.sysMsgUnread
let sysMsgUnread = temp.addFriend || 0
sysMsgUnread += temp.team || 0
let customSysMsgUnread = this.$store.state.customSysMsgUnread
return 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 = null
if (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 = null
teamInfo = this.$store.state.teamlist.find(team => {
return team.teamId === item.to
})
if (teamInfo) {
item.name = teamInfo.name
item.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())
return
location.href = '#/sysmsgs'
},
enterChat (session) {
if (this.hideDelBtn())
return
if (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.sessionId
this.stopBubble = true
setTimeout(() => {
this.stopBubble = false
}, 20)
}
},
hideDelBtn () {
if (this.delSessionId !== null && !this.stopBubble) {
// 用于判断是否前置状态是显示删除按钮
this.delSessionId = null
return 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.nim
state.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 SDK
export function initNimSDK ({ state, commit, dispatch }, loginInfo) {
if (state.nim) {
state.nim.disconnect()
}
dispatch('showLoading')
// 初始化SDK
window.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.nim
if (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 chatroom
type = type || 'nim'
switch (type) {
case 'nim':
connectNim(store, obj)
break
case 'chatroom':
connectChatroom(store, obj)
break
}
},
// 用户触发的登出逻辑
logout ({ state, commit }) {
cookie.delCookie('uid')
cookie.delCookie('sdktoken')
if (state.nim) {
state.nim.disconnect()
}
pageUtil.turnPage('', 'login')
},
// 初始化 重新连接SDK
initNimSDK,
// 清空所有搜索历史纪录
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的生命周期会执行connect
actions的调用。 connect
actions会经过initNimSDK
完成sdk的初始化- 初始化过程中会执行
onSessions
actions完成对state中sessionlist的赋值。
信息通信
接下来我们看看信息通信是怎么进行的,这个相对比较简单。
看下官方文档,或者 src/pages/components/ChatEditor.vue
关于 sendMsg
actions的调用可以轻易定位到 src/store/actions/msgs.js
文件中的sendMsg
函数。
// 发送普通消息
export function sendMsg ({state, commit}, obj) {
const nim = state.nim
obj = 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
})
break
case '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自己写一套工程出来了。