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
* 创建连接返回一个Promise 创建成功并成功打开连接算连接成功 */
* @param {*} option [connect](option) {
*/ return new Promise((resolve, reject) => {
const open = () => {
[connect](option) { console.log('[im] open');
return new Promise((resolve, reject) => { this.isOpen = true;
this.socket = uni.connectSocket({ resolve(this.socket);
...option, };
fail(e){ const message = async (res) => {
reject(e); const result = fromatPotoRsp(res.data);
} this.interceptors.onMessage && this.interceptors.onMessage(result);
}); // 处理服务端主动推送的消息
this.socket.onOpen(() => { this[onMessage](result);
this.socket.onMessage(async (res) => {
console.log(res.data,'apppppppp'); // 如果再消息堆里有此消息回调,则执行回调,并删除
const result = fromatPotoRsp(res.data); const cbk = this.queue[result[this.option.ioKey]];
// if (result.content?.payload) { if (cbk) {
// result.content.payload = JSON.parse(result.content.payload); cbk(result.code !== 200 ? { error: result } : { result: result });
// } delete this.queue[result[this.option.ioKey]];
// 401主动退出 }
if (result.code === 401) { };
this.logout(); const close = () => {
return false; console.log('[im] close');
} this.interceptors.onClose && this.interceptors.onClose();
this.interceptors.onMessage && this.interceptors.onMessage(result); };
// 处理服务端主动推送的消息 if (uni) {
this[onMessage](result); this.socket = uni.connectSocket({
...this.option,
// 如果再消息堆里有此消息回调,则执行回调,并删除 fail(e) {
const cbk = this.queue[result[this.option.ioKey]]; reject(e);
if (cbk) { },
cbk(result.code !== 200 ? {error:result} : {result:result}); });
delete this.queue[result[this.option.ioKey]]; this.socket.onOpen(() => {
} open();
}) this.socket.onMessage((res) => {
resolve(this.socket); message(res);
}); });
this.socket.onClose(() => { });
if (this.option.reconnect && !this.option.logout) { this.socket.onClose(() => {
this[connect](); close();
} });
this.option.logout = false; } else if (WebSocket) {
}); try {
}); this.socket = new WebSocket(this.option.url);
} this.socket.binaryType = 'arraybuffer';
/** this.socket.onopen = () => {
* 向服务端发送消息||请求返回一个Promise对象收到ioKey对应的消息会算一个同步完成 open();
* @param {*} data };
*/ this.socket.onmessage = (res) => {
[send](data) { message(res);
return new Promise((resolve, reject) => { };
this.queue[data[this.option.ioKey]] = ({result, error}) => { this.socket.onclose = () => {
if (result) { close();
resolve(result); };
} else { } catch (e) {
reject(error); reject(e);
} }
}; }
});
this.socket.send({ }
data : fromatPotoReq(data.traceId, data.traceType, data.content), /**
fail(e) { * 向服务端发送消息||请求返回一个Promise对象收到ioKey对应的消息会算一个同步完成
reject({error : e}); * @param {*} data
} */
}); [send](data) {
}) return new Promise((resolve, reject) => {
} if (!this.isOpen) {
/** return reject('连接未打开');
* 服务端推送消息只处理服务端主动推送的消息 }
* @param {*} data this.queue[data[this.option.ioKey]] = ({ result, error }) => {
*/ if (result) {
[onMessage](data) { resolve(result);
// 判断非服务端主动推送消息不做处理 } else {
if (data[this.option.ioKey] || data.code !== 200) { reject(error);
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); const par = fromatPotoReq(data.traceId, data.traceType, data.content);
if (uni) {
content.sessionVOS.forEach(item => { this.socket.send({
if (item.lastMessage) { data: par,
item.lastMessage.payload = JSON.parse(item.lastMessage.payload || {}); fail(e) {
} reject({ error: e });
let historyData = this.sessionData; },
let hisIndex = historyData.findIndex(i => i.id === item.id); });
if(hisIndex >= 0){ } else if (WebSocket) {
historyData[hisIndex].lastMessage = item.lastMessage; this.socket.send(par);
historyData[hisIndex].unreadCount++; }
this.setSessionData(historyData) });
}else{ }
item.messageList = []; /**
const newData = [...historyData, item] * 服务端推送消息只处理服务端主动推送的消息
this.setSessionData(newData); * @param {*} data
} */
}); [onMessage](data) {
return Promise.resolve(result); // 判断非服务端主动推送消息不做处理
} if (data[this.option.ioKey] || data.code !== 200) {
/** return false;
* 获取会话的历史消息记录 }
* @param {*} params let ctx = data.content;
*/ let historyData = [...this.sessionData],
async getHistoryMsg() { newData = [];
const curSessionIdx = this.sessionData.findIndex(i => i.id === this.curSessionId); const hisIndex = historyData.findIndex((i) => i.id === ctx.sessionId);
const curSession = this.sessionData[curSessionIdx]; if (hisIndex >= 0) {
const msgList = curSession.messageList || []; // 存在会话往现有会话增加一条消息,并修改最后一条消息为当前消息
const par = { const curHisData = historyData[hisIndex];
traceId: CreateUUID(), curHisData.messageList.push(ctx);
traceType: 23, curHisData.lastMessage = ctx;
content: { // 不在当前会话窗口则向会话消息加1条未读
sessionId: this.curSessionId, if (ctx.sessionId !== this.curSessionId) {
topMessageId: msgList.length ? msgList[0].id : null curHisData.unreadCount++;
} }
}; newData = historyData;
const {error, result} = await ToAsyncAwait(this[send](par)); } else {
if (error) { // 会话列表不存在,则创建一个会话
return Promise.reject(error); newData = [
} ...historyData,
const { content } = result; {
console.log('获取会话历史消息',par, result); fromAvatar: ctx.fromAvatar,
if (content.length) { fromId: ctx.fromId,
let newData = this.sessionData; fromNickname: ctx.fromNickname,
content.forEach(item => { id: ctx.id,
item.payload = JSON.parse(item.payload) lastMessage: ctx,
}) messageList: [ctx],
newData[curSessionIdx].messageList = content.concat(newData[curSessionIdx].messageList); unreadCount: 1,
this.setSessionData(newData); },
} ];
return Promise.resolve(result); }
this.setSessionData(newData);
} }
/** init(config) {
* 会话已读 return new Promise((resolve, reject) => {
* @param {*} params const heart = () => {
*/ // 要优化 心跳没回复需要重连
async setRead(params) { setTimeout(async () => {
const par = { if (this.isOpen) {
traceId : CreateUUID(), await this[send]({
traceType : "6", traceId: CreateUUID(),
...params traceType: 0,
} content: { text: 'ping' },
const {error, result } = await this[send](par); });
}
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,
};
console.log('会话已读', par, result); console.log('[im] 获取会话列表--start', par);
let { error, result } = await ToAsyncAwait(this[send](par));
let newData = this.sessionData.map(item => { console.log('[im] 获取会话列表--end', result, error);
if (item.id == params.content.sessionId) { if (error) {
item.unreadCount = 0; return Promise.reject(error);
} }
return item; const { content } = result;
});
this.setSessionData(newData);
}
/**
* 发送消息
* @param {*} params
*/
async sendMsg(params) {
const index = this.sessionData.findIndex(i => i.id === this.curSessionId)
let curSession = this.sessionData[index];
// 临时消息体
let par = {
...params,
traceId: CreateUUID(),
traceType: 20,
}
let msgCtx = {
...params.content,
...par,
fromId: params.fromId,
createTimeStamp : (new Date()).getTime(),
sendStatus : 'loading'
}
// 点发送,立即把消息加入消息列表,标记为发送中状态
curSession.messageList.push(msgCtx);
// 超过时间未返回视为发送失败
this.timerStatus(msgCtx);
const { error, result } = await ToAsyncAwait(this[send](par));
console.log('发送消息', par, result); content.sessionVOS.forEach((item) => {
// 接到通知,标记消息是否发送成功 if (item.lastMessage) {
for (let i = curSession.messageList.length; i--;) { item.lastMessage.payload = JSON.parse(item.lastMessage.payload || {});
const item = curSession.messageList[i]; }
if (item[this.option.ioKey] === par[this.option.ioKey]) { let historyData = this.sessionData;
curSession.messageList[i].sendStatus = msgCtx.sendStatus = error ? 'fail' : 'success'; let hisIndex = historyData.findIndex((i) => i.id === item.id);
break; if (hisIndex >= 0) {
} historyData[hisIndex].lastMessage = item.lastMessage;
} historyData[hisIndex].unreadCount++;
let newData = [...this.sessionData]; this.setSessionData(historyData);
newData[index] = curSession; } else {
this.setSessionData(newData); item.messageList = [];
if (error) { const newData = [...historyData, item];
return Promise.reject(error); 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: 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);
return Promise.resolve(result); let newData = this.sessionData.map((item) => {
} if (item.id == params.content.sessionId) {
/** item.unreadCount = 0;
* 发送失败时重新发送 }
* @param {*} params return item;
*/ });
async resend(params) {
params.sendStatus = 'loading';
this.timerStatus(params)
const { error, result } = await ToAsyncAwait(this[send]({
traceId: params.traceId,
traceType: params.traceType,
content : params.content
}));
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 { result, error } = await ToAsyncAwait(this[send]({
traceId : CreateUUID(),
traceType : 21,
...params
}));
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);
}
} this.setSessionData(newData);
}
/**
* 发送消息
* @param {*} params
*/
async sendMsg(params) {
const index = this.sessionData.findIndex((i) => i.id === this.curSessionId);
let curSession = this.sessionData[index];
// 临时消息体
let par = {
...params,
traceId: CreateUUID(),
traceType: 3,
};
let msgCtx = {
...params.content,
...par,
createTimeStamp: new Date().getTime(),
sendStatus: 'loading',
};
// 点发送,立即把消息加入消息列表,标记为发送中状态
curSession.messageList.push(msgCtx);
// 超过时间未返回视为发送失败
this.timerStatus(msgCtx);
console.log('[im] 发送消息--start', par);
const { error, result } = await ToAsyncAwait(this[send](par));
console.log('[im] 发送消息--end', result, error);
// 接到通知,标记消息是否发送成功
for (let i = curSession.messageList.length; i--; ) {
const item = curSession.messageList[i];
if (item[this.option.ioKey] === par[this.option.ioKey]) {
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);
}
/**
* 发送失败时重新发送
* @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