每條消息都要設置一個是否已讀的屬性
消息分組:
後臺返回和當前用戶相關的所有消息,根據當前用戶和不同用戶直接的聊天標識,
返回對應的分組消息列表
未讀消息:
後臺返回所有的消息後,根據和不同用戶的聊天標識,獲取相應的聊天內容,
遍歷每一條聊天內容,讀取每一條的是否已讀屬性,來確定未讀的總消息
已讀消息:
當進入聊天界面後,就通過後臺修改所有的讀取狀態屬性爲已讀的消息,
然後返回修改的條數,前臺根據修改的條數和總條數來計算剩餘未讀的總消息
代碼示例:
reducer.js:
//聊天狀態
const initChat={
users:{},
chatMsgs:[],
unReadCount:0 //總未讀數量
}
function chat(state=initChat,action)
{
switch(action.type)
{
case RECEIVE_MSG_LIST: // data: {users, chatMsgs}
const {users, chatMsgs, userid} = action.data
console.log(userid)
return {
users,
chatMsgs,
unReadCount: chatMsgs.reduce((preTotal, msg) => preTotal+(!msg.read&&msg.to===userid?1:0),0)
}
case RECEIVE_MSG: // data: chatMsg
const {chatMsg} = action.data
return {
users: state.users,
chatMsgs: [...state.chatMsgs, chatMsg],
unReadCount: state.unReadCount + (!chatMsg.read&&chatMsg.to===action.data.userid?1:0)
}
default:
return state
}
}
actions.js:
import {reqRegister,reqLogin,reqUpdateUser,reqUser,reqUserList,reqChatMsgList,reqReadMsg} from '../api/index'
import {AUTH_SUCCESS,ERROR_MSG,RECEIVE_USER,RESET_USER,RECEIVE_USERLIST,RECEIVE_MSG_LIST,RECEIVE_MSG,MSG_READ} from './action-types'
import io from 'socket.io-client'
//授權成功更改狀態
const authSuccess=(user)=>({type:AUTH_SUCCESS,data:user})
//失敗更改狀態
const errorMsg=(msg)=>({type:ERROR_MSG,data:msg})
//完善信息後更新
const receiveUser=(user)=>({type:RECEIVE_USER,data:user})
//重置用戶信息
export const resetUser=(msg)=>({type:RESET_USER,data:msg})
//接收用戶列表
const receiveUserList=(userlist)=>({type:RECEIVE_USERLIST,data:userlist})
//接收消息列表
const receiveMsgList=({users,chatMsgs,userid})=>({type:RECEIVE_MSG_LIST,data:{users,chatMsgs,userid}})
//接收單個消息
const receiveMsg=(chatMsg,userid)=>({type:RECEIVE_MSG,data:{chatMsg,userid}})
//註冊
export const register=(user)=>{
const {username,password,password2,type} =user;
//表單前臺驗證,返回對應的同步action
if(!username||!password||!password2)
{
return errorMsg('用戶名或密碼不爲空');
}
if(password!==password2)
{
return errorMsg('兩次密碼不一致');
}
return async (dispatch)=>{
const response=await reqRegister({username,password,type})
const res=response.data;
if(res.code===0) //成功
{
getMsgList(dispatch,res.data._id);
dispatch(authSuccess(res.data));
}else{
dispatch(errorMsg(res.msg));
}
}
}
//登錄
export const login=(user)=>{
const{username,password}=user
//表單前臺驗證,返回對應的action
if(!username||!password)
{
return errorMsg('用戶名或密碼不爲空');
}
return async (dispatch)=>{
const response=await reqLogin(user)
const res=response.data;
if(res.code===0) //成功
{
getMsgList(dispatch,res.data.user._id);
dispatch(authSuccess(res.data.user));
}else{
dispatch(errorMsg(res.msg));
}
}
}
//用戶完善頭像等信息後更新用戶
export const updateUser=(user)=>{
return async (dispatch)=>{
const response=await reqUpdateUser(user)
const res=response.data;
if(res.code===0)
{
dispatch(receiveUser(res.data));
}else{
dispatch(resetUser(res.msg));
}
}
}
//獲取用戶異步action
export const getUser=()=>{
return async(dispatch)=>{
const response =await reqUser();
const res=response.data;
if(res.code===0){
getMsgList(dispatch,res.data._id);
dispatch(receiveUser(res.data))
}else{
dispatch(resetUser(res.msg));
}
}
}
//獲取用戶列表
export const getUserList=(type)=>{
return async dispatch=>{
const response =await reqUserList(type);
const res=response.data;
if(res.code===0)
{
dispatch(receiveUserList(res.data))
}
}
}
/**
* 單例模式創建一個socket,利用全局對象io新增屬性來實現
*/
function initIO(dispatch,userid)
{
if(!io.socket){
io.socket=io('ws://localhost:4000')
io.socket.on('receiveMsg', function (chatMsg) {
console.log('瀏覽器端接收到消息:', chatMsg)
//只有當前會話才分發action保存
if(userid==chatMsg.from||userid==chatMsg.to)
{
dispatch(receiveMsg(chatMsg,userid))
}
})
}
}
//異步發送消息
export const sendMsg=({from,to,content})=>{
return dispatch=>{
initIO();
console.log('瀏覽器發送消息',{from,to,content})
io.socket.emit('sendMsg',{from,to,content})
}
}
//異步獲取消息函數
async function getMsgList(dispatch,userid)
{
initIO(dispatch,userid);
const response =await reqChatMsgList();
const res=response.data
if(res.code===0)
{
const{users,chatMsgs}=res.data
dispatch(receiveMsgList({users,chatMsgs,userid}))
}
}
action-type.js:
//註冊/登錄成功
export const AUTH_SUCCESS='auth_success'
//錯誤提示信息
export const ERROR_MSG='error_msg'
//用戶信息完善
export const RECEIVE_USER='receive_user'
//重置用戶
export const RESET_USER='reset_user'
//接收用戶列表
export const RECEIVE_USERLIST='receive_userlist'
//接收消息列表
export const RECEIVE_MSG_LIST='receive_msg_list'
//接收到新的一條消息
export const RECEIVE_MSG='receive_msg'
//讀取消息
export const MSG_READ='msg_read'
api接口:
/*
包含n個接口請求的函數
*/
import ajax from './ajax'
//註冊接口
export const reqRegister=(user)=>ajax('/register',user,'POST');
//登錄接口
export const reqLogin=({username,password})=>ajax('/login',{username,password},'POST');
//更新用戶接口
export const reqUpdateUser=(user)=>ajax('/update',user,'POST');
//獲取用戶信息
export const reqUser=()=>ajax('/user')
//獲取用戶列表
export const reqUserList=(type)=>ajax('/userlist',{type:type})
//獲取聊天消息
export const reqChatMsgList=()=>ajax('/msglist')
//修改消息爲已讀
export const reqReadMsg=(from)=>ajax('/readMsg',{from},'POST')
api封裝:
/**
ajax模塊,返回值爲promise對象
*/
import axios from 'axios'
import qs from 'qs'
export default function ajax(url,data={},type='GET')
{
// url='http://127.0.0.1:4000'+url;
if(type==='GET')
{
let str=''
//將對象拼成url參數對
Object.keys(data).forEach(function(item,index){
str+=item+'='+data[item]+'&'
})
//去掉最後一個&或根本無參數
if(str)
{
str=str.substring(0,str.length-1)
str='?'+str;
}
return axios.get(url+str)
}else{
return axios.post(url,qs.stringify(data))
}
}
後臺消息有關路由:
let express = require('express')
var bodyParser = require('body-parser');
var md5=require('blueimp-md5');
var cookieParser = require('cookie-parser');
var urlencodedParser = bodyParser.urlencoded({ extended: false });
const {UserModel,ChatModel} =require('./db/models');
const filter={password:0,__v:0};
var app=express();
//socket.io
var server = require('http').Server(app);
require('./socketio/test')(server);
server.listen(4000,function(){
console.log('this express server is running at http://127.0.0.1:4000 ');
});
app.use(cookieParser());
/*
獲取當前用戶所有相關聊天信息列表
*/
app.get('/msglist', (req, res)=>{
// 獲取 cookie 中的 userid
const userid = req.cookies.userid
console.log(userid);
// 查詢得到所有 user 文檔數組
UserModel.find(function (err, userDocs) {
// 用對象存儲所有 user 信息: key 爲 user 的_id, val 爲 name 和 header 組成的 user 對象
const users = {} // 對象容器
//將數據庫的所有用戶,按照以user_id爲key,存入容器中
userDocs.forEach(doc => {
users[doc._id] = {username: doc.username, header: doc.header}
})
/*
查詢 userid 相關的所有聊天信息
參數 1: 查詢條件
參數 2: 過濾條件
參數 3: 回調函數
*/
//根據兩個用戶其中一個userid,返回所有聊天內容
ChatModel.find({'$or': [{from: userid}, {to: userid}]}, filter, function (err,chatMsgs) {
// 返回包含所有用戶和當前用戶相關的所有聊天消息的數據
res.send({code: 0, data: {users, chatMsgs}})
})
})
})
/*
修改指定消息爲已讀
*/
app.post('/readmsg',urlencodedParser, function (req, res) {
// 得到請求中的 from 和 to
const from = req.body.from
const to = req.cookies.userid
console.log(from);
/*
更新數據庫中的 chat 數據
參數 1: 查詢條件
參數 2: 更新爲指定的數據對象
參數 3: 是否 1 次更新多條, 默認只更新一條
參數 4: 更新完成的回調函數
*/
ChatModel.update({from, to, read: false}, {read: true}, {multi: true}, function (err,doc) {
console.log('/readmsg', doc)
res.send({code: 0, data: doc.nModified}) // 更新的數量
})
})
socket.io後臺模塊:
const {ChatModel}=require('../db/models')
module.exports = function (server) {
// 得到 IO 對象
const io = require('socket.io')(server)
//監視連接(當有一個客戶連接上時回調)
io.on('connection', function (socket) {
console.log('soketio connected')
// 綁定 sendMsg 監聽, 接收客戶端發送的消息
socket.on('sendMsg', function ({from,to,content}) {
console.log('服務器接收客戶端發送',{from,to,content})
//保存消息進數據庫,再將保存信息返回給前臺
const chat_id=[from,to].sort().join('-');
const create_time=Date.now();
new ChatModel({from,to,content,chat_id,create_time}).save(function(err,chatMsg){
//向客戶端發送數據
io.emit('receiveMsg',chatMsg);
})
})
})
}