接口
router.js
// 推送群公告 router.post('/group/remark',controller.group.remark);
app/controller/group.js
'use strict'; const Controller = require('egg').Controller; class GroupController extends Controller { // 获取群聊列表 async list() { const { ctx, app } = this; let current_user_id = ctx.authUser.id; let page = ctx.params.page ? parseInt(ctx.params.page) : 1; let limit = ctx.query.limit ? parseInt(ctx.query.limit) : 10; let offset = (page - 1) * limit; let rows = await app.model.Group.findAll({ where: { status: 1 }, include: [{ model: app.model.GroupUser, where: { user_id: current_user_id } }] }); return ctx.apiSuccess(rows); } // 创建群聊 async create() { const { ctx,app } = this; // 拿到当前用户id let current_user_id = ctx.authUser.id; // 验证参数 ctx.validate({ ids:{ require:true, type:'array' } }); let { ids } = ctx.request.body; // 验证是否是我的好友 let friends = await app.model.Friend.findAll({ where:{ user_id:current_user_id, friend_id:ids }, include:[{ model:app.model.User, as:'friendInfo', attributes:['nickname','username'] }] }); if (!friends.length) { return ctx.apiFail('请选择需要加入群聊的好友'); } // 创建群聊 let name = friends.map(item=>item.friendInfo.nickname || item.friendInfo.username); name.push(ctx.authUser.nickname || ctx.authUser.username); // 将自己的数据加入 let group = await app.model.Group.create({ name:name.join(','), avatar:'', user_id:current_user_id }); // 加入群聊用户 let data = friends.map(item=>{ return {user_id:item.friend_id,group_id:group.id} }); data.unshift({ user_id:current_user_id, group_id:group.id }); await app.model.GroupUser.bulkCreate(data); // 消息推送 let message = { id:(new Date()).getTime(), // 唯一id,后端生成唯一id from_avatar:ctx.authUser.avatar,// 发送者头像 from_name:ctx.authUser.nickname || ctx.authUser.username,// 发送者昵称 from_id:current_user_id, // 发送者id to_id:group.id,// 接收人id to_name:group.name,// 接收人/群 名称 to_avatar:group.avatar,// 接收人/群 头像 chat_type:'group', // 接收类型 type:'system', // 消息类型 data:'创建群聊成功,可以聊天了', // 消息内容 options:{}, // 其他参数 create_time:(new Date()).getTime(),// 创建时间 isremove:0, // 是否撤回 group:group } data.forEach(item =>{ ctx.sendAndSaveMessage(item.user_id,message); }); ctx.apiSuccess('ok'); } // 查看群资料 async info() { const { ctx, app } = this; let current_user_id = ctx.authUser.id; // 验证参数 ctx.validate({ id: { required: true, type: 'int', desc: "群组id" } }); let { id } = ctx.params; // 群组是否存在 let group = await app.model.Group.findOne({ where: { status: 1, id }, include: [{ model: app.model.GroupUser, attributes: ['user_id', 'nickname'], include: [{ model: app.model.User, attributes: ['id', 'nickname', 'avatar', 'username'] }] }] }); if (!group) { return ctx.apiFail('该群聊不存在或者已被封禁'); } // 当前用户是否是该群成员 let index = group.group_users.findIndex(item => item.user_id === current_user_id); if (index === -1) { return ctx.apiFail('你不是该群成员,没有权限'); } ctx.apiSuccess(group); } // 修改群名称 async rename(){ const { ctx,app } = this; let current_user_id = ctx.authUser.id; // 参数验证 ctx.validate({ id:{ required:true, type:'int', desc:'群组id' }, name:{ required:true, type:'string', desc:'群名称' } }); let { id,name } = ctx.request.body; // 是否存在 let group = await app.model.Group.findOne({ where:{ id, status:1 }, include:[{ model:app.model.GroupUser, attributes:['user_id','nickname'] }] }); if(!group){ return ctx.apiFail('该群聊不存在或者已被封禁'); } // 当前用户是否是该群成员 let index = group.group_users.findIndex(item=>item.user_id === current_user_id); if(index === -1){ return ctx.apiFail('你不是该群成员'); } // 验证是否是群主 if(group.user_id !== current_user_id){ return ctx.apiFail('你不是管理员,没有权限'); } // 修改群名称 group.name = name; await group.save(); let from_name = group.group_users[index].nickname || ctx.authUser.nickname || ctx.authUser.username; // 消息推送 let message = { id:(new Date()).getTime(), // 唯一id,后端生成唯一id from_avatar:ctx.authUser.avatar,// 发送者头像 from_name,// 发送者昵称 from_id:current_user_id, // 发送者id to_id:group.id,// 接收人id to_name:group.name,// 接收人/群 名称 to_avatar:group.name,// 接收人/群 头像 chat_type:'group', // 接收类型 type:'system', // 消息类型 data:`${from_name} 修改群名称为 ${name}`, // 消息内容 options:{}, // 其他参数 create_time:(new Date()).getTime(),// 创建时间 isremove:0, // 是否撤回 group:group } // 推送消息 group.group_users.forEach(item=>{ ctx.sendAndSaveMessage(item.user_id,message); }); ctx.apiSuccess('ok'); } // 推送群公告 async remark(){ const { ctx,app } = this; let current_user_id = ctx.authUser.id; // 参数验证 ctx.validate({ id:{ required:true, type:'int', desc:'群组id' }, remark:{ required:true, type:'string', desc:'群公告' } }); let { id,remark } = ctx.request.body; // 是否存在 let group = await app.model.Group.findOne({ where:{ id, status:1 }, include:[{ model:app.model.GroupUser, attributes:['user_id','nickname'] }] }); if(!group){ return ctx.apiFail('该群聊不存在或者已被封禁'); } // 当前用户是否是该群成员 let index = group.group_users.findIndex(item=>item.user_id === current_user_id); if(index === -1){ return ctx.apiFail('你不是该群成员'); } // 验证是否是群主 if(group.user_id !== current_user_id){ return ctx.apiFail('你不是管理员,没有权限'); } // 修改群公告 group.remark = remark; await group.save(); let from_name = group.group_users[index].nickname || ctx.authUser.nickname || ctx.authUser.username; // 消息推送 let message = { id:(new Date()).getTime(), // 唯一id,后端生成唯一id from_avatar:ctx.authUser.avatar,// 发送者头像 from_name,// 发送者昵称 from_id:current_user_id, // 发送者id to_id:group.id,// 接收人id to_name:group.name,// 接收人/群 名称 to_avatar:group.name,// 接收人/群 头像 chat_type:'group', // 接收类型 type:'system', // 消息类型 data:`[新公告] ${remark}`, // 消息内容 options:{}, // 其他参数 create_time:(new Date()).getTime(),// 创建时间 isremove:0, // 是否撤回 group:group } // 推送消息 group.group_users.forEach(item=>{ ctx.sendAndSaveMessage(item.user_id,message); }); ctx.apiSuccess('ok'); } } module.exports = GroupController;
页面
chat.js
import $U from "./util.js"; import $H from './request.js'; class chat { constructor(arg) { this.url = arg.url this.isOnline = false this.socket = null // 获取当前用户相关信息 let user = $U.getStorage('user'); this.user = user ? JSON.parse(user) : {}, // 初始化聊天对象 this.TO = false; // 连接和监听 if (this.user.token) { this.connectSocket() } } // 连接socket connectSocket() { console.log(this.user); this.socket = uni.connectSocket({ url: this.url + '?token=' + this.user.token, complete: () => {} }) // 监听连接成功 this.socket.onOpen(() => this.onOpen()) // 监听接收信息 this.socket.onMessage((res) => this.onMessage(res)) // 监听断开 this.socket.onClose(() => this.onClose()) // 监听错误 this.socket.onError(() => this.onError()) } // 监听打开 onOpen() { // 用户状态上线 this.isOnline = true; console.log('socket连接成功'); // 获取用户离线消息 this.getMessage(); } // 获取离线消息 getMessage(){ $H.post('/chat/getmessage'); } // 监听关闭 onClose() { // 用户下线 this.isOnline = false; this.socket = null; console.log('socket连接关闭'); } // 监听消息 onMessage(data) { console.log('监听消息', data); let res = JSON.parse(data.data) // console.log('监听接收消息',res) // 错误 switch (res.msg) { case 'fail': return uni.showToast({ title: res.data, icon: 'none' }); break; case 'recall': // 撤回消息 this.handleOnRecall(res.data) break; case 'updateApplyList': // 新的好友申请 $store.dispatch('getApply'); break; case 'moment': // 朋友圈更新 this.handleMoment(res.data) break; default: // 处理消息 this.handleOnMessage(res.data) break; } } // 处理消息 async handleOnMessage(message) { // 添加消息记录到本地存储中 let { data } = this.addChatDetail(message, false) // 更新会话列表 this.updateChatList(data, false) // 全局通知 uni.$emit('onMessage', data) // 消息提示 // this.messageNotice() } // 监听连接错误 onError() { // 用户下线 this.isOnline = false; this.socket = null; console.log('socket连接错误'); } // 关闭连接 close() { this.socket.close() } // 创建聊天对象 createChatObject(detail) { this.TO = detail; console.log('创建聊天对象', this.TO) } // 销毁聊天对象 destoryChatObject() { this.TO = false } // 组织发送信息格式 formatSendData(params) { return { id: 0, // 唯一id,后端生成,用于撤回指定消息 from_avatar: this.user.avatar, // 发送者头像 from_name: this.user.nickname || this.user.username, // 发送者昵称 from_id: this.user.id, // 发送者id to_id: params.to_id || this.TO.id, // 接收人/群 id to_name: params.to_name || this.TO.name, // 接收人/群 名称 to_avatar: params.to_avatar || this.TO.avatar, // 接收人/群 头像 chat_type: params.chat_type || this.TO.chat_type, // 接收类型 type: params.type, // 消息类型 data: params.data, // 消息内容 options: params.options ? params.options : {}, // 其他参数 create_time: (new Date()).getTime(), // 创建时间 isremove: 0, // 是否撤回 sendStatus: params.sendStatus ? params.sendStatus : "pending" // 发送状态,success发送成功,fail发送失败,pending发送中 } } // 发送信息 send(message, onProgress = false) { return new Promise((result, reject) => { // 添加消息历史记录 // this.addChatDetail(); let { k } = this.addChatDetail(message); // 更新会话列表 this.updateChatList(message); // 验证是否上线 if (!this.checkOnLine()) return reject('未上线'); // 上传文件 let isUpload = (message.type !== 'text' && message.type !== 'emoticon' && message.type !== 'card' && !message.data.startsWith('http://tangzhe123-com')) let uploadResult = '' if (isUpload) { uploadResult = $H.upload('/upload', { filePath: message.data }, onProgress) if (!uploadResult) { // 发送失败 message.sendStatus = 'fail' // 更新指定历史记录 this.updateChatDetail(message, k) // 断线重连提示 return reject(err) } } $H.post('/chat/send', { to_id: this.TO.id, type: message.type, chat_type: this.TO.chat_type, data: message.data, }).then(res => { // 发送成功 console.log('chat.js发送成功'); message.id = res.id message.sendStatus = 'success'; // 更新指定历史记录 this.updateChatDetail(message, k); result(res); }).catch(err => { // 发送失败 console.log('chat.js发送失败'); message.sendStatus = 'fail'; // 更新指定历史记录 this.updateChatDetail(message, k); // 断线重连提示 result(err); }); }) } // 验证是否上线 checkOnLine() { if (!this.isOnline) { // 断线重连提示 this.reconnectConfirm(); return false; } return true; } // 断线重连提示 reconnectConfirm() { uni.showModal({ title: '你已经断线,是否重新连接?', content: '重新连接', success: res => { if (res.confirm) { this.connectSocket(); } }, }); } // 添加聊天记录 addChatDetail(message, isSend = true) { console.log('添加到聊天记录'); // 获取对方id let id = message.chat_type === 'user' ? (isSend ? message.to_id : message.from_id) : message.to_id; if (!id) { return { data: {}, k: 0 } } // key值:chatDetail_当前用户id_会话类型_接收人/群id let key = `chatDetail_${this.user.id}_${message.chat_type}_${id}`; console.log(key); // 获取原来的聊天记录 let list = this.getChatdetail(key) console.log('获取原来的聊天记录', list); // 标识 message.k = 'k' + list.length list.push(message) // 加入存储 console.log('加入存储', message); this.setStorage(key, list); // 返回 return { data: message, k: message.k } } // 更新指定历史记录 async updateChatDetail(message, k, isSend = true) { // 获取对方id let id = message.chat_type === 'user' ? (isSend ? message.to_id : message.from_id) : message.to_id; // key值:chatDetail_当前用户id_会话类型_接收人/群id let key = `chatDetail_${this.user.id}_${message.chat_type}_${id}`; // 获取原来的聊天记录 let list = this.getChatdetail(key); // 根据k查找对应聊天记录 let index = list.findIndex(item => item.k === k); if (index === -1) return; list[index] = message; // 存储 this.setStorage(key, list); } // 获取聊天记录 getChatdetail(key = false) { key = key ? key : `chatDetail_${this.user.id}_${this.TO.chat_type}_${this.TO.id}`; return this.getStorage(key); } // 格式化会话最后一条消息显示 formatChatItemData(message, isSend) { let data = message.data.length > 18 ? message.data.slice(0,17) + '...' : message.data; switch (message.type) { case 'emoticon': data = '[表情]' break; case 'image': data = '[图片]' break; case 'audio': data = '[语音]' break; case 'video': data = '[视频]' break; case 'card': data = '[名片]' break; } data = isSend ? data : `${message.from_name}: ${data}` return data } // 更新会话列表 updateChatList(message, isSend = true) { // 获取本地存储会话列表 let list = this.getChatList() // 是否处于当前聊天中 let isCurrentChat = false // 接收人/群 id/头像/昵称 let id = 0 let avatar = '' let name = '' // 判断私聊还是群聊 if (message.chat_type === 'user') { // 私聊 // 聊天对象是否存在 isCurrentChat = this.TO ? (isSend ? this.TO.id === message.to_id : this.TO.id === message.from_id) : false id = isSend ? message.to_id : message.from_id avatar = isSend ? message.to_avatar : message.from_avatar name = isSend ? message.to_name : message.from_name } else { // 群聊 isCurrentChat = this.TO && (this.TO.id === message.to_id) id = message.to_id avatar = message.to_avatar name = message.to_name } // 会话是否存在 let index = list.findIndex(item => { return item.chat_type === message.chat_type && item.id === id }) // 最后一条消息展现形式 // let data = isSend ? message.data : `${message.from_name}: ${message.data}` let data = this.formatChatItemData(message, isSend) // 会话不存在,创建会话 // 未读数是否 + 1 let noreadnum = (isSend || isCurrentChat) ? 0 : 1 if (index === -1) { let chatItem = { id, // 接收人/群 id chat_type: message.chat_type, // 接收类型 user单聊 group群聊 avatar, // 接收人/群 头像 name, // 接收人/群 昵称 update_time: (new Date()).getTime(), // 最后一条消息的时间戳 data, // 最后一条消息内容 type: message.type, // 最后一条消息类型 noreadnum, // 未读数 istop: false, // 是否置顶 shownickname: false, // 是否显示昵称 nowarn: false, // 消息免打扰 strongwarn: false, // 是否开启强提醒 } // 群聊 if (message.chat_type === 'group' && message.group) { chatItem.shownickname = true chatItem.name = message.to_name chatItem = { ...chatItem, user_id: message.group.user_id, // 群管理员id remark: "", // 群公告 invite_confirm: 1, // 邀请确认 } list.unshift(chatItem) } } else { // 存在,更新会话 // 拿到当前会话 let item = list[index] // 更新该会话最后一条消息时间,内容,类型 item.update_time = (new Date()).getTime() item.name = message.to_name item.data = data item.type = message.type // 未读数更新 item.noreadnum += noreadnum // 置顶会话 list = this.listToFirst(list, index) } // 存储 let key = `chatlist_${this.user.id}` this.setStorage(key, list) // 更新未读数 this.updateBadge(list) // 通知更新vuex中的聊天会话列表 uni.$emit('onUpdateChatList', list) return list } // 获取聊天记录 getChatList(message, isSend = true) { // 获取本地存储会话列表 let list = this.getChatList(); // 是否处在当前聊天中 let isCurrentChat = false // 接收人/群 id/头像/昵称 let id = 0; let avatar = ''; let name = ''; // 判断私聊还是群聊 if (message.chat_type === 'user') { // 私聊 isCurrentChat = this.TO ? (isSend ? this.TO.id === message.to_id : message.from_id) : false; id = isSend ? message.to_id : message.from_id; avatar = isSend ? message.to_avatar : message.from_avatar name = isSend ? message.to_name : message.from_name } else { // 群聊 } // 会话是否存在 let index = list.findIndex(item => { return item.chat_type === message.chat_type && item.id === id; }) // 最后一条消息展现形式 let data = isSend ? message.data : `${message.from_name}:${message.data}`; // 未读数是否 +1 let noreadnum = (isSend || isCurrentChat) ? 0 : 1; // 会话不存在 创建会话 if (index === -1) { let chatItem = { id, // 接收人/群 id chat_type: message.chat_type, // 接收类型 user 单聊 group群聊 name, // 接收人/群 昵称 avatar, // 接收人/群 头像 update_time: (new Date()).getTime(), // 最后发送的时间 data, // 最后一条消息的内容 type: message.type, noreadnum: 1, // 未读数 istop: false, // 是否置顶 shownickname: false, // 是否显示昵称 nowarn: false, // 是否免打扰 strongwarn: false, // 是否强提醒 } if (message.chat_type === 'group') { chatItem = { ...chatItem, user_id: 0, // 管理员id remark: '', // 群公告 invite_confirm: 0 // 邀请确认 } } list.unshift(chatItem) } else { // 存在,更新会话 // 拿到当前会话 let item = list[index] // 更新改会话最后一条消息时间,内容,类型 item.update_time = (new Date()).getTime(); item.data = data; item.type = message.type; // 未读数更新 item.noreadnum += noreadnum // 置顶会话 list = this.listToFirst(list, index); } // 存储 let key = `chatlist_${this.user.id}`; this.setStorage(key, list); // 更新未读数 this.updateBadge(list); // 更新vuex中的聊天会话列表 uni.$emit('onUpdateChatList', list); console.log('获取到的会话列表:',list) return list; /** * { id:1, // 接收人/群 id chat_type:'user', // 接收类型 user 单聊 group群聊 name:'昵称', // 接收人/群 昵称 avatar:"/static/images/demo/demo6.jpg", // 接收人/群 头像 type:'',// 最后一条消息类型 update_time:1628069958, // 最后发送的时间 data:"你好啊,哈哈哈", // 最后一条消息的内容 noreadnum:1, // 未读数 istop:false, // 是否置顶 shownickname:0, // 是否显示昵称 nowarn:0, // 是否免打扰 strongwarn:0, // 是否强提醒 user_id://管理员id, remark:'公告', // 群公告 invite_confirm:0, // 邀请确认 }, **/ } // 获取本地存储会话列表 getChatList() { let key = `chatlist_${this.user.id}`; return this.getStorage(key); } // 读取会话 async readChatItem(id,chat_type){ // 获取所有会话列表 let list = this.getChatList(); // 找到当前会话 let index = list.findIndex(item=>item.id === id && item.chat_type === chat_type); if(index !== -1){ list[index].noreadnum = 0; let key = `chatlist_${this.user.id}`; this.setStorage(key,list); // 重新获取未读数 this.updateBadge(); // 更新会话列表状态 uni.$emit('onUpdateChatList',list); } } // 获取指定会话 getChatListItem(id,chat_type){ // 获取所有会话列表 let list = this.getChatList() // 找到当前会话 let index = list.findIndex(item=>item.id === id && item.chat_type === chat_type) if(index !== -1){ return list[index] } return false } // 更新未读数 async updateBadge(list = false) { // 获取所有会话列表 list = list ? list : this.getChatList() // 统计所有未读数 let total = 0 list.forEach(item => { total += item.noreadnum }) // 设置底部导航栏角标 if (total > 0) { uni.setTabBarBadge({ index: 0, text: total <= 99 ? total.toString() : '99+' }) } else { uni.removeTabBarBadge({ index: 0 }) } uni.$emit('totalNoreadnum', total) } // 获取存储 getStorage(key) { let list = $U.getStorage(key); return list ? JSON.parse(list) : []; } // 设置存储 setStorage(key, value) { return $U.setStorage(key, JSON.stringify(value)); } // 数组置顶 listToFirst(arr, index) { if (index != 0) { arr.unshift(arr.splice(index, 1)[0]); } return arr; } } export default chat
我测试的结果,如下图
感谢大家观看,我们下次见