/app.js
class AppBootHook { constructor(app) { this.app = app; } configWillLoad() { // 此时 config 文件已经被读取并合并,但是还并未生效 // 这是应用层修改配置的最后时机 // 注意:此函数只支持同步调用 } async didLoad() { // 所有的配置已经加载完毕 // const app = this.app; // const ctx = await app.createAnonymousContext(); // app.messenger.send // 可以用来加载应用自定义的文件,启动自定义的服务 } async willReady() { // 所有的插件都已启动完毕,但是应用整体还未 ready // 可以做一些数据初始化等操作,这些操作成功才会启动应用 // 例如:从数据库加载数据到内存缓存 } async didReady() { // 应用已经启动完毕 const app = this.app; const ctx = await app.createAnonymousContext(); app.messenger.on('offline', user_id => { if (app.ws.user[user_id]) { app.ws.user[user_id].send(JSON.stringify({ msg: "fail", data: '你的账号在其他设备登录' })); app.ws.user[user_id].close(); } }); app.messenger.on('send', e => { let { to_id, message, msg } = e; if (app.ws.user && app.ws.user[to_id]) { app.ws.user[to_id].send(JSON.stringify({ msg, data: message })); app.ws.user[user.id].close(); } }); } async serverDidReady() { // http / https server 已启动,开始接受外部请求 // 此时可以从 app.server 拿到 server 的实例 } } module.exports = AppBootHook;
/app/controller/chat.js
// app/controller/chat.js const Controller = require('egg').Controller; class ChatController extends Controller { // 连接socket async connect() { const { ctx, app, service } = this; if (!ctx.websocket) { ctx.throw(400, '非法访问'); } // console.log(`clients: ${app.ws.clients.size}`); // 监听接收消息和关闭socket ctx.websocket .on('message', msg => { // console.log('接收消息', msg); }) .on('close', (code, reason) => { // 用户下线 console.log('用户下线', code, reason); let user_id = ctx.websocket.user_id; // 移除redis中的用户上线记录 service.cache.remove('online_' + user_id); if (app.ws.user && app.ws.user[user_id]) { delete app.ws.user[user_id]; } }); } // 发送消息 async send() { const { ctx, app, service } = this; // 拿到当前用户id let current_user_id = ctx.authUser.id; // 验证参数 ctx.validate({ to_id: { type: 'int', required: true, desc: '接收人/群id' }, chat_type: { type: 'string', required: true, range: { in: ['user', 'group'] }, desc: '接收类型' }, type: { type: 'string', required: true, range: { in: ['text', 'image', 'video', 'audio', 'emoticon', 'card'] }, desc: '消息类型' }, data: { type: 'string', required: true, desc: '消息内容' }, options: { type: 'string', required: true } }); // 获取参数 let { to_id, chat_type, type, data, options } = ctx.request.body; // 单聊 if (chat_type === 'user') { // 验证好友是否存在,并且对方没有把你拉黑 let Friend = await app.model.Friend.findOne({ where: { user_id: to_id, friend_id: current_user_id, isblack: 0 }, include: [{ model: app.model.User, as: "userInfo" }, { model: app.model.User, as: "friendInfo" }] }); if (!Friend) { return ctx.apiFail('对方不存在或者已经把你拉黑'); } // 验证好友是否被禁用 if (!Friend.userInfo.status) { return ctx.apiFail('对方已被禁用'); } // 构建消息格式 let from_name = Friend.friendInfo.nickname ? Friend.friendInfo.nickname : Friend.friendInfo.username; if (Friend.nickname) { from_name = Friend.nickname; } let message = { id: (new Date()).getTime(), // 唯一id,后端生成唯一id from_avatar: Friend.friendInfo.avatar,// 发送者头像 from_name, // 发送者昵称 from_id: current_user_id, // 发送者id to_id, // 接收人/群 id to_name: Friend.userInfo.nickname ? Friend.userInfo.nickname : Friend.userInfo.username, // 接收人/群 名称 to_avatar: Friend.userInfo.avatar, // 接收人/群 头像 chat_type: 'user', // 接收类型 type,// 消息类型 data, // 消息内容 options: {}, // 其他参数 create_time: (new Date()).getTime(), // 创建时间 isremove: 0, // 是否撤回 } // 视频,截取封面 if (message.type === 'video') { message.options.poster = message.data + '?x-oss-process=video/snapshot,t_10,m_fast,w_300,f_png'; } // 音频,带上音频时长 if (message.type === 'audio') { options = JSON.parse(options); message.options.time = options.time || 1; } // 名片 if (message.type === 'card') { // 验证名片用户是否存在 message.options = JSON.parse(options) } ctx.sendAndSaveMessage(to_id, message); // 存储到自己的聊天记录中 chatlog_当前用户id_user_对方用户id service.cache.setList(`chatlog_${current_user_id}_${message.chat_type}_${to_id}`, message); // 返回成功 return ctx.apiSuccess(message); } // 群聊 // 验证群聊是否存在,且你是否在该群中 let group = await app.model.Group.findOne({ where: { status: 1, id: to_id }, 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('你不是该群的成员'); } // 组织数据格式 let from_name = group.group_users[index].nickname; let message = { id: (new Date()).getTime(), // 唯一id,后端生成唯一id from_avatar: ctx.authUser.avatar,// 发送者头像 from_name: from_name || ctx.authUser.nickname || ctx.authUser.username, // 发送者昵称 from_id: current_user_id, // 发送者id to_id, // 接收人/群 id to_name: group.name, // 接收人/群 名称 to_avatar: group.avatar, // 接收人/群 头像 chat_type: 'group', // 接收类型 type,// 消息类型 data, // 消息内容 options: {}, // 其他参数 create_time: (new Date()).getTime(), // 创建时间 isremove: 0, // 是否撤回 group: group } // 视频,截取封面 if (message.type === 'video') { message.options.poster = message.data + '?x-oss-process=video/snapshot,t_10,m_fast,w_300,f_png'; } // 音频,带上音频时长 if (message.type === 'audio') { options = JSON.parse(options); message.options.time = options.time || 1; } // 名片 if (message.type === 'card') { // 验证名片用户是否存在 message.options = JSON.parse(options) } // 推送消息 group.group_users.forEach(item => { if (item.user_id !== current_user_id) { ctx.sendAndSaveMessage(item.user_id, message); } }); ctx.apiSuccess(message); } // 获取离线消息 async getmessage() { const { ctx, app, service } = this; let current_user_id = ctx.authUser.id; let key = 'getmessage_' + current_user_id; let list = await service.cache.getList(key); // 清除离线消息 await service.cache.remove(key); // 批量推送 list.forEach(async (message) => { let d = JSON.parse(message); ctx.sendAndSaveMessage(current_user_id, d.message, d.msg); }); } // 撤回 async recall() { const { ctx, app } = this; let current_user_id = ctx.authUser.id; ctx.validate({ to_id: { type: 'int', required: true, desc: '接收人/群id' }, chat_type: { type: 'string', required: true, range: { in: ['user', 'group'] }, desc: '接收类型' }, id: { type: 'int', required: true, desc: '消息id' } }); let { to_id, chat_type, id } = ctx.request.body; let message = { from_id: current_user_id, to_id, chat_type, id } // 单聊 if (chat_type === 'user') { ctx.sendAndSaveMessage(to_id, message, 'recall'); return ctx.apiSuccess(message); } // 群聊 let group = await app.model.Group.findOne({ where: { id: to_id, status: 1 }, include: [{ model: app.model.GroupUser, attributes: ['user_id'] }] }); if (group) { group.group_users.forEach(item => { if (item.user_id !== current_user_id) { ctx.sendAndSaveMessage(item.user_id, message, 'recall'); } }); } return ctx.apiSuccess(message); } } module.exports = ChatController;
/app/extend/extend.js
// app/extend/context.js var qr = require('qr-image'); module.exports = { // 成功提示 apiSuccess(data = '', msg = 'ok', code = 200) { this.body = { msg, data }; this.status = code; }, // 失败提示 apiFail(data = '', msg = 'fail', code = 400) { this.body = { msg, data }; this.status = code; }, // 生成token getToken(value) { return this.app.jwt.sign(value, this.app.config.jwt.secret); }, // 验证token checkToken(token) { return this.app.jwt.verify(token, this.app.config.jwt.secret); }, // 发送或者存到消息队列中 sendAndSaveMessage(to_id,message,msg='ok'){ const { app,service } = this; let current_user_id = this.authUser.id; // 拿到当前的socket // let socket = app.ws.user[to_id]; // 验证对方是否在线,不在线记录到待接收消息队列中 在线:消息推送 存储到对方的聊天记录中 chatlog_对方用户id_user_当前用户id if(app.ws.user && app.ws.user[to_id]){ // 消息推送 app.ws.user[to_id].send(JSON.stringify({ msg, data:message })) // 存到历史记录中 if(msg === 'ok'){ service.cache.setList(`chatlog_${to_id}_${message.chat_type}_${current_user_id}`,message); } }else{ service.cache.setList('getmessage_'+to_id,{ message, msg }); } }, // 生成二维码 qrcode(url){ var img = qr.image(url,{size:10}); this.response.type = 'image/png'; this.body = img; }, // 生成唯一id genID(length) { return Number(Math.random().toString().substr(3, length) + Date.now()).toString(36); }, // 用户上线 async online(user_id,pid){ const {app,service} = this; // 下线其他设备 let pid = service.cache.get('online_'+user_id); if(pid){ // 通知对应进程用户下线 app.messager.sendTo(pid,'offline',user_id); } // 存储上线状态 service.cache.set('online_'+user_id,pid); } };
感谢大家观看,我们下次见