VChat - 一个通讯社交类WebApp,本文用于记录开发遇到的磕磕绊绊和技术点。
使用JWT(JSON WEB TOKEN)做接口权限设计
接口权限,即在客户端发送请求的时候,需要带上一个服务器给予的token,服务器根据该token判断该客户端是否可以获取资源或进行某些操作。
exm:
登陆成功时生成token给到客户端,客户端把token存在本地,每次请求时带上。
router.post('/signin',(req,res)=>{
let pars = req.body,
username = pars.username,
password = pars.password,
sql = $userSql.getByAccount;
// 根据username 找出用户信息 利用uid生成token
conn.query(sql,username,(err,result)=>{
if(result[0]){
let user = result[0]
if(user.upassword === password){
let secret = 'i am a good man'; //密钥
//设置token
let token = jwt.sign({ user }, secret, { expiresIn: 60 * 60 * 1 });
res.json({'result':'登陆成功','token':token,'uid':user.uid})
}else{
res.json({'result':'密码错误'})
}
}else{
res.json({'result':'用户不存在'})
}
});
});
客户端发送请求,服务端验证token
router.get('/friends',(req,res)=>{
let token = req.query.token,
secret = 'i am a good man',//密钥
friSql = $friSql.getById,
userSql = $userSql.getById;
jwt.verify(token, secret, function (err, decode) {
if (err) { // 时间失效 或者 遇到伪造的token
res.json({'info':err})
} else {
// decode中就含有我们用于生成token的信息,我用的是uid
let user = decode.user;
let uid = user.uid;
}
});
});
// 前端使用axios请求
this.$axios.get('/api/user/friends',{
params:{
token: window.localStorage.getItem('token')
}
}).then(res=>{
if(res.data['result']){
this.friends = res.data['result']
}
})
使用Socket.io 做点对点通信
流程:
1.用户登陆成功后,与服务器发起socket连接。
2.服务器使用一个users对象来保存用户与socket之间的映射,每当socket断开,都要更新users。每次用户断开连接时,都要将users[uid] 置为 null。每次连接时再分配新的socketId。
let users = {
uid1: socketId,
uid2: socketId
}
3.在sender发送信息给receiver时,是利用receiver的uid去获取其socketId来发送,但如果receiver不在线,其socketId自然为null,这时候需要先让服务器保存sender发送的信息,在下一次receiver连接socketId时将这些信息发给receiver。
// 存放离线信息
let unline_data = [
// uid2 为接收方
uid2 : [
{
senderId :
msg :
},
.......
]
];
每次客户端上线时检查有没有离线信息给该客户端的,有的话发送,并清空该客户端离线信息。
// 连接成功
let hisMsg = unline_data[conn_uid] ? unline_data[conn_uid]:'没有新消息';
let initMsg = {
msg: `${conn_uid} 连接成功 给你分配的socketId为${users[conn_uid].id}`,
hisMsg: hisMsg
};
socket.emit('receiveMsg',{
msg: initMsg,
code: 200
});
unline_data[conn_uid] = null; // 清空离线信息
完整代码
let users = {
// 'uid':'socketId'
};
// 存放离线信息
let unline_data = [
/*
// uid2 为接收方
uid2 : [
{
senderId :
msg :
},
.......
]
*/
];
io.on('connection',function(socket) {
// 存储连接用户的uid 和 socketId映射
let conn_uid = socket.request._query.uid;
users[conn_uid] = socket;
// 连接成功
let hisMsg = unline_data[conn_uid]? unline_data[conn_uid]:'没有新消息';
let initMsg = {
msg: `${conn_uid} 连接成功 给你分配的socketId为${users[conn_uid].id}`,
hisMsg: hisMsg
};
socket.emit('receiveMsg',{
msg: initMsg,
code: 200
});
unline_data[conn_uid] = null; // 清空离线信息
// 这里监听 disconnect,就可以知道谁断开连接了
socket.on('disconnect', function () {
Object.keys(users).forEach(k=>{
if (users[k] && users[k]===socket){
users[k] = false
}
});
console.log('socket disconnect: ' + socket.id);
});
//接收数据 并转发给相应的用户 实现单点聊天
socket.on('sendMsg', function (obj) {
let uid = obj.uid;
let uid2 = obj.uid2;
let msg = obj.msg;
let data = {
senderId: uid,
receiverId: uid2,
msg: msg
};
// 接收方在线(socket连接中)
if(users[uid2]){
users[uid2].emit('receiveMsg',data);
}else{
// 对方不在线,用数据结构先纯起来
if(! (Object.prototype.toString.call(unline_data[uid2]).slice(8,-1) === 'Array')){
unline_data[uid2] = []
}
unline_data[uid2].push(data)
}
});
});
用Vuex管理Socket
router.beforeEach((to,from,next)=>{
let uid = window.localStorage.getItem('uid');
let socket = store.getters.getSocket;
// store中没有 就重新连接 并提交到store中
if (!socket){
store.dispatch('setSocket',io.connect('//192.168.137.1:3000',{
'force new connection': true,
'query': {
uid:uid
}
}));//与服务器进行连接
//监听接收信息
store.getters.getSocket.on("receiveMsg",function (data) {
let msg = data.msg;
console.log(msg);
});
}
next() // 继续跳转路由
});
Vuex:
import Vue from 'vue'
import Vuex from 'vuex'
import io from 'socket.io-client'
Vue.use(Vuex)
const store = new Vuex.Store({
state:{
headTitle:'聊天',
socket:null
},
getters: {
getHeadTitle: function (state) {
return state.headTitle;
},
getSocket:function(state){
return state.socket
}
},
mutations:{
setHeadTitleFunc:function(state,title){
state.headTitle = title;
},
setSocketFunc:function(state,socket){
state.socket = socket
}
},
actions:{
setHeadTitle:function(context,title){
context.commit('setHeadTitleFunc',title)
},
setSocket:function(context,socket){
context.commit('setSocketFunc',socket)
}
}
});
export default store