feature/im-0602-ch
ch 3 years ago
parent 0bdab1c4bf
commit 51ee94d573

@ -2,7 +2,7 @@
* @Author: ch * @Author: ch
* @Date: 2022-05-05 14:40:00 * @Date: 2022-05-05 14:40:00
* @LastEditors: ch * @LastEditors: ch
* @LastEditTime: 2022-06-02 15:09:46 * @LastEditTime: 2022-06-09 11:25:44
* @Description: 根据git分支生成对应环境的环境变量 * @Description: 根据git分支生成对应环境的环境变量
* 开发时如果环境变量换了可以不用重启服务直接运行node env.config.js即可 * 开发时如果环境变量换了可以不用重启服务直接运行node env.config.js即可
*/ */
@ -13,8 +13,8 @@ const envConfig = {
dev : { dev : {
baseUrl: 'https://k8s-horse-gateway.mashibing.cn', baseUrl: 'https://k8s-horse-gateway.mashibing.cn',
staticUrl : 'https://k8s-shop-app.mashibing.cn', staticUrl : 'https://k8s-shop-app.mashibing.cn',
imUrl : 'ws://192.168.10.94:8090' // imUrl : 'ws://192.168.10.94:8090'
// imUrl : 'wss://k8s-horse-gateway.mashibing.cn' imUrl : 'wss://k8s-horse-gateway.mashibing.cn'
}, },
test : { test : {
baseUrl: 'https://k8s-horse-gateway.mashibing.cn', baseUrl: 'https://k8s-horse-gateway.mashibing.cn',

@ -2,7 +2,7 @@
* @Author: ch * @Author: ch
* @Date: 2022-05-27 17:44:36 * @Date: 2022-05-27 17:44:36
* @LastEditors: ch * @LastEditors: ch
* @LastEditTime: 2022-06-02 14:45:51 * @LastEditTime: 2022-06-09 11:34:36
* @Description: file content * @Description: file content
*/ */
import {ToAsyncAwait, MsbRequestTk} from '@/common/utils'; import {ToAsyncAwait, MsbRequestTk} from '@/common/utils';
@ -11,4 +11,6 @@ const BASE_URL = '/mall/im';
/** /**
* 获取soket登录秘钥 * 获取soket登录秘钥
*/ */
export const ApiGetSoketTicket = () => ToAsyncAwait(MsbRequestTk.get(`${BASE_URL}/ticket`)); export const ApiGetSoketTicket = () => ToAsyncAwait(MsbRequestTk.get(`${BASE_URL}/ticket`, {
ticketType: 'CONNECT_TICKET'
}));

@ -2,406 +2,439 @@
* @Author: ch * @Author: ch
* @Date: 2022-05-18 14:54:47 * @Date: 2022-05-18 14:54:47
* @LastEditors: ch * @LastEditors: ch
* @LastEditTime: 2022-06-07 19:28:14 * @LastEditTime: 2022-06-09 11:38:48
* @Description: file content * @Description: file content
*/ */
import { CreateUUID, FormatDate, ToAsyncAwait } from "@/common/utils"; import { CreateUUID, FormatDate, ToAsyncAwait } from "@/common/utils";
import './potoReq'; import './potoReq';
import './protoRsp' import './protoRsp'
const connect = Symbol('connect'), const connect = Symbol('connect');
send = Symbol('send'), const send = Symbol('send');
onResponse = Symbol('onResponse'), const onMessage = Symbol('onMessage');
onMessage = Symbol('onMessage'),
updateData = Symbol('updateData')
;
const fromatPotoReq = (traceId, traceType, content) => { const fromatPotoReq = (traceId, traceType, content) => {
let messageModel = new proto.ReqModel(); let messageModel = new proto.ReqModel();
messageModel.setTraceid(traceId); messageModel.setTraceid(traceId);
messageModel.setTracetype(traceType); messageModel.setTracetype(traceType);
content && messageModel.setContent(JSON.stringify(content)); content && messageModel.setContent(JSON.stringify(content));
return messageModel.serializeBinary(); return messageModel.serializeBinary();
}, },
fromatPotoRsp = (data)=>{ fromatPotoRsp = (data) => {
const res = proto.RspModel.deserializeBinary(new Uint8Array(data)); const res = proto.RspModel.deserializeBinary(new Uint8Array(data));
let ctx = res.getContent(); let ctx = res.getContent();
ctx = ctx ? JSON.parse(ctx) : {}; ctx = ctx ? JSON.parse(ctx) : {};
if (ctx.payload) { if (ctx.payload) {
ctx.payload = JSON.parse(ctx.payload) ctx.payload = JSON.parse(ctx.payload);
} }
return { return {
content: ctx, content: ctx,
traceId: res.getTraceid(), traceId: res.getTraceid(),
traceType: res.getTracetype(), traceType: res.getTracetype(),
code: res.getCode(), code: res.getCode(),
message : res.getMessage() message: res.getMessage(),
} };
} };
export default class MsbIm { class MsbIm {
option = { defaultOption = {
ioKey : 'traceId', ioKey: 'traceId',
reconnect: true, reconnect: true,
logout : false };
} socket = null;
socket = null; isOpen = false;
isOpen = false; queue = {};
queue = {}; interceptors = {
interceptors = { dataChangeBefore: null,
dataChangeBefore: null, dataChangeAfter: null,
dataChangeAfter: null, onClose: null,
onLogout : null, onMessage: null,
onMessage: null };
sessionData = [];
}; curSessionId = null;
sessionData = []; constructor(option) {
curSessionId = null; this.option = {
...this.defaultOption,
constructor(option) { ...option,
this.option = { };
...this.option, }
...option
}
}
/**
* 创建连接返回一个Promise 创建成功并成功打开连接算连接成功
* @param {*} option
*/
[connect](option) {
return new Promise((resolve, reject) => {
this.socket = uni.connectSocket({
...option,
fail(e){
reject(e);
}
});
this.socket.onOpen(() => {
this.socket.onMessage(async (res) => {
console.log(res.data,'apppppppp');
const result = fromatPotoRsp(res.data);
// if (result.content?.payload) {
// result.content.payload = JSON.parse(result.content.payload);
// }
// 401主动退出
if (result.code === 401) {
this.logout();
return false;
}
this.interceptors.onMessage && this.interceptors.onMessage(result);
// 处理服务端主动推送的消息
this[onMessage](result);
// 如果再消息堆里有此消息回调,则执行回调,并删除
const cbk = this.queue[result[this.option.ioKey]];
if (cbk) {
cbk(result.code !== 200 ? {error:result} : {result:result});
delete this.queue[result[this.option.ioKey]];
}
})
resolve(this.socket);
});
this.socket.onClose(() => {
if (this.option.reconnect && !this.option.logout) {
this[connect]();
}
this.option.logout = false;
});
});
}
/**
* 向服务端发送消息||请求返回一个Promise对象收到ioKey对应的消息会算一个同步完成
* @param {*} data
*/
[send](data) {
return new Promise((resolve, reject) => {
this.queue[data[this.option.ioKey]] = ({result, error}) => {
if (result) {
resolve(result);
} else {
reject(error);
}
};
this.socket.send({
data : fromatPotoReq(data.traceId, data.traceType, data.content),
fail(e) {
reject({error : e});
}
});
})
}
/**
* 服务端推送消息只处理服务端主动推送的消息
* @param {*} data
*/
[onMessage](data) {
// 判断非服务端主动推送消息不做处理
if (data[this.option.ioKey] || data.code !== 200) {
return false;
}
let ctx = data.content;
let historyData = [...this.sessionData],
newData = [];
const hisIndex = historyData.findIndex(i => i.id === ctx.sessionId);
if(hisIndex >= 0){
// 存在会话往现有会话增加一条消息,并修改最后一条消息为当前消息
const curHisData = historyData[hisIndex];
curHisData.messageList.push(ctx);
curHisData.lastMessage = ctx;
// 不在当前会话窗口则向会话消息加1条未读
if(ctx.sessionId !== this.curSessionId){
curHisData.unreadCount++;
}
newData = historyData;
}else{
// 会话列表不存在,则创建一个会话
newData = [...historyData, {
fromAvatar : ctx.fromAvatar,
fromId : ctx.fromId,
fromNickname : ctx.fromNickname,
id : ctx.id,
lastMessage : ctx,
messageList : [ctx],
unreadCount : 1
}]
}
this.setSessionData(newData)
}
init (config) {
return new Promise((resolve, reject) => {
const heart = () => {
setTimeout(async () => {
await this[send]({
traceId: CreateUUID(),
traceType: 0,
content: { text: "ping" }
}).catch((e)=>{});
heart();
},1000)
}
this[connect]({
...config,
}).then(() => {
heart();
resolve(this);
}).catch((e)=>{});
})
}
logout() {
this.option.logout = true;
this.socket.close();
this.interceptors.onLogout && this.interceptors.onLogout();
}
/**
* 设置数据
*/
setSessionData(data) {
this.interceptors.dataChangeBefore && this.interceptors.dataChangeBefore(data, this.sessionData);
this.sessionData = data;
this.interceptors.dataChangeAfter && this.interceptors.dataChangeAfter(this.sessionData);
}
/**
* 设置当前聊天窗口
* Data为Session数据
* @param {*} data
*/
setCurSessionId(id) {
this.curSessionId = id;
}
/**
* 获取会话列表
* @param {*} params
*/
async getSessionList(params) {
const par = {
traceId: CreateUUID(),
traceType: 1,
...params
};
let {error, result} = await ToAsyncAwait(this[send](par));
if (error) {
return Promise.reject(error);
}
const { content } = result;
console.log('获取会话列表', par, result); /**
* 创建连接返回一个Promise 创建成功并成功打开连接算连接成功
* @param {*} option
*/
[connect](option) {
return new Promise((resolve, reject) => {
const open = () => {
console.log('[im] open');
this.isOpen = true;
resolve(this.socket);
};
const message = async (res) => {
const result = fromatPotoRsp(res.data);
this.interceptors.onMessage && this.interceptors.onMessage(result);
// 处理服务端主动推送的消息
this[onMessage](result);
content.sessionVOS.forEach(item => { // 如果再消息堆里有此消息回调,则执行回调,并删除
if (item.lastMessage) { const cbk = this.queue[result[this.option.ioKey]];
item.lastMessage.payload = JSON.parse(item.lastMessage.payload || {});
}
let historyData = this.sessionData;
let hisIndex = historyData.findIndex(i => i.id === item.id);
if(hisIndex >= 0){
historyData[hisIndex].lastMessage = item.lastMessage;
historyData[hisIndex].unreadCount++;
this.setSessionData(historyData)
}else{
item.messageList = [];
const newData = [...historyData, item]
this.setSessionData(newData);
}
});
return Promise.resolve(result);
}
/**
* 获取会话的历史消息记录
* @param {*} params
*/
async getHistoryMsg() {
const curSessionIdx = this.sessionData.findIndex(i => i.id === this.curSessionId);
const curSession = this.sessionData[curSessionIdx];
const msgList = curSession.messageList || [];
const par = {
traceId: CreateUUID(),
traceType: 23,
content: {
sessionId: this.curSessionId,
topMessageId: msgList.length ? msgList[0].id : null
}
};
const {error, result} = await ToAsyncAwait(this[send](par));
if (error) {
return Promise.reject(error);
}
const { content } = result;
console.log('获取会话历史消息',par, result);
if (content.length) {
let newData = this.sessionData;
content.forEach(item => {
item.payload = JSON.parse(item.payload)
})
newData[curSessionIdx].messageList = content.concat(newData[curSessionIdx].messageList);
this.setSessionData(newData);
}
return Promise.resolve(result);
} if (cbk) {
/** cbk(result.code !== 200 ? { error: result } : { result: result });
* 会话已读 delete this.queue[result[this.option.ioKey]];
* @param {*} params }
*/ };
async setRead(params) { const close = () => {
const par = { console.log('[im] close');
traceId : CreateUUID(), this.interceptors.onClose && this.interceptors.onClose();
traceType : "6", };
...params if (uni) {
} this.socket = uni.connectSocket({
const {error, result } = await this[send](par); ...this.option,
fail(e) {
reject(e);
},
});
this.socket.onOpen(() => {
open();
this.socket.onMessage((res) => {
message(res);
});
});
this.socket.onClose(() => {
close();
});
} else if (WebSocket) {
try {
this.socket = new WebSocket(this.option.url);
this.socket.binaryType = 'arraybuffer';
this.socket.onopen = () => {
open();
};
this.socket.onmessage = (res) => {
message(res);
};
this.socket.onclose = () => {
close();
};
} catch (e) {
reject(e);
}
}
});
}
/**
* 向服务端发送消息||请求返回一个Promise对象收到ioKey对应的消息会算一个同步完成
* @param {*} data
*/
[send](data) {
return new Promise((resolve, reject) => {
if (!this.isOpen) {
return reject('连接未打开');
}
this.queue[data[this.option.ioKey]] = ({ result, error }) => {
if (result) {
resolve(result);
} else {
reject(error);
}
};
console.log('会话已读', par, result); const par = fromatPotoReq(data.traceId, data.traceType, data.content);
if (uni) {
this.socket.send({
data: par,
fail(e) {
reject({ error: e });
},
});
} else if (WebSocket) {
this.socket.send(par);
}
});
}
/**
* 服务端推送消息只处理服务端主动推送的消息
* @param {*} data
*/
[onMessage](data) {
// 判断非服务端主动推送消息不做处理
if (data[this.option.ioKey] || data.code !== 200) {
return false;
}
let ctx = data.content;
let historyData = [...this.sessionData],
newData = [];
const hisIndex = historyData.findIndex((i) => i.id === ctx.sessionId);
if (hisIndex >= 0) {
// 存在会话往现有会话增加一条消息,并修改最后一条消息为当前消息
const curHisData = historyData[hisIndex];
curHisData.messageList.push(ctx);
curHisData.lastMessage = ctx;
// 不在当前会话窗口则向会话消息加1条未读
if (ctx.sessionId !== this.curSessionId) {
curHisData.unreadCount++;
}
newData = historyData;
} else {
// 会话列表不存在,则创建一个会话
newData = [
...historyData,
{
fromAvatar: ctx.fromAvatar,
fromId: ctx.fromId,
fromNickname: ctx.fromNickname,
id: ctx.id,
lastMessage: ctx,
messageList: [ctx],
unreadCount: 1,
},
];
}
this.setSessionData(newData);
}
init(config) {
return new Promise((resolve, reject) => {
const heart = () => {
// 要优化 心跳没回复需要重连
setTimeout(async () => {
if (this.isOpen) {
await this[send]({
traceId: CreateUUID(),
traceType: 0,
content: { text: 'ping' },
});
}
heart();
}, 1000);
};
this.option = {
...this.option,
...config,
};
this[connect]()
.then((res) => {
resolve(res);
heart();
})
.catch((e) => {
console.log('eeeee', e);
});
});
}
/**
* 设置数据
*/
setSessionData(data) {
let newData = JSON.parse(JSON.stringify(data));
this.interceptors.dataChangeBefore && this.interceptors.dataChangeBefore(newData, this.sessionData);
this.sessionData = newData;
this.interceptors.dataChangeAfter && this.interceptors.dataChangeAfter(this.sessionData);
}
/**
* 设置当前聊天窗口
* Data为Session数据
* @param {*} data
*/
setCurSessionId(id) {
this.curSessionId = id;
}
/**
* 获取会话列表
* @param {*} params
*/
async getSessionList(params) {
const par = {
traceId: CreateUUID(),
traceType: 1,
...params,
};
let newData = this.sessionData.map(item => { console.log('[im] 获取会话列表--start', par);
if (item.id == params.content.sessionId) { let { error, result } = await ToAsyncAwait(this[send](par));
item.unreadCount = 0; console.log('[im] 获取会话列表--end', result, error);
} if (error) {
return item; return Promise.reject(error);
}); }
const { content } = result;
this.setSessionData(newData); content.sessionVOS.forEach((item) => {
} if (item.lastMessage) {
/** item.lastMessage.payload = JSON.parse(item.lastMessage.payload || {});
* 发送消息 }
* @param {*} params let historyData = this.sessionData;
*/ let hisIndex = historyData.findIndex((i) => i.id === item.id);
async sendMsg(params) { if (hisIndex >= 0) {
const index = this.sessionData.findIndex(i => i.id === this.curSessionId) historyData[hisIndex].lastMessage = item.lastMessage;
let curSession = this.sessionData[index]; historyData[hisIndex].unreadCount++;
// 临时消息体 this.setSessionData(historyData);
let par = { } else {
...params, item.messageList = [];
traceId: CreateUUID(), const newData = [...historyData, item];
traceType: 20, this.setSessionData(newData);
} }
let msgCtx = { });
...params.content, return Promise.resolve(result);
...par, }
fromId: params.fromId, /**
createTimeStamp : (new Date()).getTime(), * 获取会话的历史消息记录
sendStatus : 'loading' * @param {*} params
} */
// 点发送,立即把消息加入消息列表,标记为发送中状态 async getHistoryMsg() {
curSession.messageList.push(msgCtx); const curSessionIdx = this.sessionData.findIndex((i) => i.id === this.curSessionId);
// 超过时间未返回视为发送失败 const curSession = this.sessionData[curSessionIdx];
this.timerStatus(msgCtx); const msgList = curSession.messageList || [];
const { error, result } = await ToAsyncAwait(this[send](par)); const par = {
traceId: CreateUUID(),
traceType: 2,
content: {
sessionId: this.curSessionId,
topMessageId: msgList.length ? msgList[0].id : null,
},
};
console.log('[im] 获取会话历史消息--start', par);
const { error, result } = await ToAsyncAwait(this[send](par));
console.log('[im] 获取会话历史消息--end', result, error);
if (error) {
return Promise.reject(error);
}
const { content } = result;
if (content.length) {
let newData = this.sessionData;
content.forEach((item) => {
item.payload = JSON.parse(item.payload);
});
newData[curSessionIdx].messageList = content.concat(newData[curSessionIdx].messageList);
this.setSessionData(newData);
}
return Promise.resolve(result);
}
/**
* 会话已读
* @param {*} params
*/
async setRead(params) {
const par = {
traceId: CreateUUID(),
traceType: '6',
...params,
};
console.log('[im] 会话已读--start', par);
const { error, result } = await this[send](par);
console.log('[im] 会话已读--end', result, error);
console.log('发送消息', par, result); let newData = this.sessionData.map((item) => {
// 接到通知,标记消息是否发送成功 if (item.id == params.content.sessionId) {
for (let i = curSession.messageList.length; i--;) { item.unreadCount = 0;
const item = curSession.messageList[i]; }
if (item[this.option.ioKey] === par[this.option.ioKey]) { return item;
curSession.messageList[i].sendStatus = msgCtx.sendStatus = error ? 'fail' : 'success'; });
break;
}
}
let newData = [...this.sessionData];
newData[index] = curSession;
this.setSessionData(newData);
if (error) {
return Promise.reject(error);
}
return Promise.resolve(result); this.setSessionData(newData);
} }
/** /**
* 发送失败时重新发送 * 发送消息
* @param {*} params * @param {*} params
*/ */
async resend(params) { async sendMsg(params) {
params.sendStatus = 'loading'; const index = this.sessionData.findIndex((i) => i.id === this.curSessionId);
this.timerStatus(params) let curSession = this.sessionData[index];
const { error, result } = await ToAsyncAwait(this[send]({ // 临时消息体
traceId: params.traceId, let par = {
traceType: params.traceType, ...params,
content : params.content traceId: CreateUUID(),
})); traceType: 3,
params.createTimeStamp = result.createTimeStamp; };
if (error) { let msgCtx = {
params.sendStatus = 'fail'; ...params.content,
return Promise.reject(error); ...par,
} createTimeStamp: new Date().getTime(),
params.sendStatus = 'success'; sendStatus: 'loading',
return Promise.resolve(result); };
} // 点发送,立即把消息加入消息列表,标记为发送中状态
timerStatus(msg) { curSession.messageList.push(msgCtx);
// 超过时间未返回视为发送失败
this.timerStatus(msgCtx);
setTimeout(() => { console.log('[im] 发送消息--start', par);
if (msg.sendStatus === 'loading') { const { error, result } = await ToAsyncAwait(this[send](par));
msg.sendStatus = 'fail'; console.log('[im] 发送消息--end', result, error);
delete this.queue[msg.traceId]; // 接到通知,标记消息是否发送成功
} for (let i = curSession.messageList.length; i--; ) {
}, 3000); const item = curSession.messageList[i];
} if (item[this.option.ioKey] === par[this.option.ioKey]) {
/** curSession.messageList[i].sendStatus = msgCtx.sendStatus = error ? 'fail' : 'success';
* 主动创建会话 break;
* @param {*} params }
*/ }
async createSession (params){ let newData = [...this.sessionData];
const { result, error } = await ToAsyncAwait(this[send]({ newData[index] = curSession;
traceId : CreateUUID(), this.setSessionData(newData);
traceType : 21, if (error) {
...params return Promise.reject(error);
})); }
if (error) {
return Promise.reject(error);
}
const { content } = result;
let historyData = this.sessionData;
let curSession = historyData.find(i => i.id === content.id);
if (!curSession) {
curSession = {
...content,
unreadCount: 0,
messageList : []
}
const newData = [...historyData, curSession];
this.setSessionData(newData);
}
return Promise.resolve(result);
}
return Promise.resolve(result);
}
/**
* 发送失败时重新发送
* @param {*} params
*/
async resend(params) {
params.sendStatus = 'loading';
this.timerStatus(params);
console.log('[im] 重新发送消息--start', params);
const { error, result } = await ToAsyncAwait(
this[send]({
traceId: params.traceId,
traceType: params.traceType,
content: params.content,
})
);
console.log('[im] 重新发送消息--end', result, error);
params.createTimeStamp = result.createTimeStamp;
if (error) {
params.sendStatus = 'fail';
return Promise.reject(error);
}
params.sendStatus = 'success';
return Promise.resolve(result);
}
timerStatus(msg) {
setTimeout(() => {
if (msg.sendStatus === 'loading') {
msg.sendStatus = 'fail';
delete this.queue[msg.traceId];
}
}, 3000);
}
/**
* 主动创建会话
* @param {*} params
*/
async createSession(params) {
const par = {
traceId: CreateUUID(),
traceType: 9,
...params,
};
console.log('[im] 主动创建会话--start', par);
const { result, error } = await ToAsyncAwait(this[send](par));
console.log('[im] 主动创建会话--end', result, error);
if (error) {
return Promise.reject(error);
}
const { content } = result;
let historyData = this.sessionData;
let curSession = historyData.find((i) => i.id === content.id);
if (!curSession) {
curSession = {
...content,
unreadCount: 0,
messageList: [],
};
const newData = [...historyData, curSession];
this.setSessionData(newData);
}
return Promise.resolve(result);
}
} }
export default MsbIm;

@ -2,7 +2,7 @@
* @Author: ch * @Author: ch
* @Date: 2022-05-20 11:00:07 * @Date: 2022-05-20 11:00:07
* @LastEditors: ch * @LastEditors: ch
* @LastEditTime: 2022-06-09 10:00:15 * @LastEditTime: 2022-06-09 11:32:37
* @Description: file content * @Description: file content
*/ */

@ -2,7 +2,7 @@
* @Author: ch * @Author: ch
* @Date: 2021-07-26 23:22:16 * @Date: 2021-07-26 23:22:16
* @LastEditors: ch * @LastEditors: ch
* @LastEditTime: 2022-06-02 16:11:56 * @LastEditTime: 2022-06-09 11:43:04
* @Description: file content * @Description: file content
*/ */
import Vue from 'vue'; import Vue from 'vue';

@ -2,7 +2,7 @@
* @Author: ch * @Author: ch
* @Date: 2022-03-26 14:32:03 * @Date: 2022-03-26 14:32:03
* @LastEditors: ch * @LastEditors: ch
* @LastEditTime: 2022-06-02 17:33:13 * @LastEditTime: 2022-06-09 11:41:50
* @Description: file content * @Description: file content
--> -->
<template> <template>
@ -138,7 +138,8 @@ export default {
async createSessionMain(){ async createSessionMain(){
const {error, result} = await ToAsyncAwait(Im.createSession({ const {error, result} = await ToAsyncAwait(Im.createSession({
content : { content : {
storeId : 1 // storeId : 1,
sessionType : 3
} }
})); }));

Loading…
Cancel
Save