This commit is contained in:
wxl 2025-01-10 00:28:12 +08:00
parent 2e07ea8a71
commit f2348ef1f5
9 changed files with 248 additions and 152 deletions

View File

@ -1,8 +1,10 @@
package com.dd.admin.business.chat.controller;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.extra.pinyin.PinyinUtil;
import com.dd.admin.business.chat.domain.AuthorChat;
import com.dd.admin.business.chat.domain.MessageBean;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import org.springframework.web.bind.annotation.*;
import io.swagger.annotations.Api;
@ -18,7 +20,7 @@ import com.dd.admin.business.chat.domain.ChatVo;
import com.dd.admin.business.chat.domain.ChatDto;
import com.dd.admin.business.chat.service.ChatService;
import java.util.List;
import java.util.*;
import org.springframework.web.bind.annotation.RestController;
@ -37,18 +39,63 @@ public class ChatController {
@Autowired
ChatService chatService;
/**
* 将给定的Date对象转换为对应东八区Asia/Shanghai的时间戳
*
* @param createTime 要转换的Date对象
* @return 对应东八区的时间戳以毫秒为单位
*/
public static long convertToShanghaiTimeZoneTimestamp(Date createTime) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(createTime);
calendar.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
return calendar.getTimeInMillis();
}
@ApiOperation(value = "作者列表")
@ApiOperationSupport(order = 2)
@GetMapping("/admin/chat/authorList")
public ResultBean<List<AuthorChat>> authorList() {
List<AuthorChat> authorChats = chatService.selectAuthorChatList();
public ResultBean<List<AuthorChat>> authorList(String authorId) {
List<AuthorChat> authorChats = chatService.selectAuthorChatList(authorId);
authorChats.stream().forEach(authorChat -> {
authorChat.setIndex(String.valueOf(PinyinUtil.getFirstLetter(authorChat.getIndex().charAt(0))));
if(authorChat.getCreateTime()!=null){
authorChat.setLastSendTime(convertToShanghaiTimeZoneTimestamp( authorChat.getCreateTime()));
}
});
return ResultBean.success(authorChats);
}
@ApiOperation(value = "作者相关聊天信息")
@ApiOperationSupport(order = 2)
@GetMapping("/admin/chat/getAuthorChat")
public ResultBean<IPage<MessageBean>> getAuthorChat(String authorId, String fromId) {
ChatDto chatDto = new ChatDto();
chatDto.setFromId(fromId);
chatDto.setToId(authorId);
IPage chatPage = chatService.selectChatPage(chatDto);
List<ChatVo> records = chatPage.getRecords();
List<MessageBean> messageBeanList = new ArrayList<>();
records.stream().forEach(chatVo -> {
MessageBean messageBean = new MessageBean();
messageBean.setId(chatVo.getChatId());
messageBean.setContent(chatVo.getContent());
messageBean.setSendTime(convertToShanghaiTimeZoneTimestamp(chatVo.getCreateTime()));
messageBean.setStatus("succeed");
messageBean.setType("text");
messageBean.setToContactId(chatVo.getToId());
MessageBean.FromUser fromUser = new MessageBean.FromUser();
fromUser.setAvatar(chatVo.getFromAvatar());
fromUser.setDisplayName(chatVo.getFromName());
fromUser.setId(chatVo.getFromId());
messageBean.setFromUser(fromUser);
messageBeanList.add(messageBean);
});
chatPage.setRecords(messageBeanList);
return ResultBean.success(chatPage);
}
@ApiOperation(value = "-分页列表")

View File

@ -1,10 +1,13 @@
package com.dd.admin.business.chat.domain;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
@Data
@AllArgsConstructor
@NoArgsConstructor
@ -36,4 +39,7 @@ public class AuthorChat {
// 最近一条消息的发送时间通常是时间戳形式单位可能是毫秒
@ApiModelProperty(value = "最近一条消息发送时间")
private Long lastSendTime;
@JsonIgnore
private Date CreateTime;
}

View File

@ -119,7 +119,8 @@
wa.AVATAR_URL avatar,
wa.AUTHOR_NAME AS 'index',
wb.unReadCount unRead,
UNIX_TIMESTAMP(CONVERT_TZ(wb.CREATE_TIME, '+08:00', '+00:00')) lastSendTime
wb.content lastContent,
wb.create_time
FROM
business_author wa
LEFT JOIN (

View File

@ -1 +1 @@
package com.dd.admin.business.webSocket.handler; import cn.hutool.core.bean.BeanUtil; import com.dd.admin.business.chat.domain.ChatVo; import com.dd.admin.business.chat.entity.Chat; import com.dd.admin.business.chat.service.ChatService; import com.dd.admin.business.webSocket.MsgHandlerInterface; import com.dd.admin.business.webSocket.util.TioUtil; import com.dd.admin.common.utils.AddressUtils; import com.dd.admin.common.utils.HttpContext; import com.dd.admin.common.utils.IPUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; import org.tio.core.ChannelContext; import org.tio.core.Tio; import org.tio.http.common.HttpRequest; import org.tio.utils.lock.SetWithLock; import javax.annotation.PostConstruct; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Date; import java.util.Map; import java.util.Set; @Component @Slf4j @Service("5") public class P2PMessageHandler implements MsgHandlerInterface { public static P2PMessageHandler handler; @Autowired ChatService chatService; @Override public Object handler(Map map, ChannelContext context ){ Chat chat = BeanUtil.toBean(map, Chat.class); chat.setIpAddress(context.getClientNode().getIp()); chat.setIpRealAddress(AddressUtils.getRealAddress(chat.getIpAddress())); //ip真实地址 chatService.save(chat); ChatVo chatVo = chatService.selectChat(chat.getChatId()); TioUtil.sendChatMessageToUser(context.getGroupContext(),chat.getToId(),"5",chatVo); TioUtil.sendChatMessageToUser(context.getGroupContext(),chat.getFromId(),"5",chatVo); return null; }}
package com.dd.admin.business.webSocket.handler; import cn.hutool.core.bean.BeanUtil; import com.dd.admin.business.chat.domain.ChatVo; import com.dd.admin.business.chat.domain.MessageBean; import com.dd.admin.business.chat.entity.Chat; import com.dd.admin.business.chat.service.ChatService; import com.dd.admin.business.webSocket.MsgHandlerInterface; import com.dd.admin.business.webSocket.util.TioUtil; import com.dd.admin.common.utils.AddressUtils; import com.dd.admin.common.utils.HttpContext; import com.dd.admin.common.utils.IPUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; import org.tio.core.ChannelContext; import org.tio.core.Tio; import org.tio.http.common.HttpRequest; import org.tio.utils.lock.SetWithLock; import javax.annotation.PostConstruct; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.*; @Component @Slf4j @Service("5") public class P2PMessageHandler implements MsgHandlerInterface { public static P2PMessageHandler handler; @Autowired ChatService chatService; public static long convertToShanghaiTimeZoneTimestamp(Date createTime) { Calendar calendar = Calendar.getInstance(); calendar.setTime(createTime); calendar.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai")); return calendar.getTimeInMillis(); } @Override public Object handler(Map map, ChannelContext context ){ Chat chat = BeanUtil.toBean(map, Chat.class); chat.setIpAddress(context.getClientNode().getIp()); chat.setIpRealAddress(AddressUtils.getRealAddress(chat.getIpAddress())); //ip真实地址 chatService.save(chat); ChatVo chatVo = chatService.selectChat(chat.getChatId()); if(chat.getToId().equals("8")){ MessageBean messageBean = new MessageBean(); messageBean.setId(chatVo.getChatId()); messageBean.setContent(chatVo.getContent()); messageBean.setSendTime(convertToShanghaiTimeZoneTimestamp(chatVo.getCreateTime())); messageBean.setStatus("succeed"); messageBean.setType("text"); messageBean.setToContactId(chatVo.getToId()); MessageBean.FromUser fromUser = new MessageBean.FromUser(); fromUser.setAvatar(chatVo.getFromAvatar()); fromUser.setDisplayName(chatVo.getFromName()); fromUser.setId(chatVo.getFromId()); messageBean.setFromUser(fromUser); System.out.println(messageBean); TioUtil.sendChatMessageToUser(context.getGroupContext(),chat.getToId(),"6",messageBean); }else{ TioUtil.sendChatMessageToUser(context.getGroupContext(),chat.getToId(),"5",chatVo); } TioUtil.sendChatMessageToUser(context.getGroupContext(),chat.getFromId(),"5",chatVo); return null; }}

View File

@ -1 +1 @@
package com.dd.admin.business.webSocket.handler; import cn.hutool.core.bean.BeanUtil; import com.dd.admin.business.chat.domain.ChatVo; import com.dd.admin.business.chat.domain.MessageBean; import com.dd.admin.business.chat.entity.Chat; import com.dd.admin.business.chat.service.ChatService; import com.dd.admin.business.webSocket.MsgHandlerInterface; import com.dd.admin.business.webSocket.util.TioUtil; import com.dd.admin.common.utils.AddressUtils; import lombok.extern.slf4j.Slf4j; import org.checkerframework.checker.units.qual.C; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; import org.tio.core.ChannelContext; import java.util.Map; @Component @Slf4j @Service("6") public class ServiceMessageHandler implements MsgHandlerInterface { public static ServiceMessageHandler handler; @Autowired ChatService chatService; @Override public Object handler(Map map, ChannelContext context ){ System.out.println(map); MessageBean messageBean = BeanUtil.toBean(map, MessageBean.class); //xx发送人 MessageBean.FromUser fromUser = messageBean.getFromUser(); Chat chat = new Chat(); chat.setFromId(fromUser.getId()); chat.setFromName(fromUser.getDisplayName()); chat.setToId(messageBean.getToContactId()); chat.setContent(messageBean.getContent()); chat.setIpAddress(context.getClientNode().getIp()); chat.setIpRealAddress(AddressUtils.getRealAddress(chat.getIpAddress())); //ip真实地址 chatService.save(chat); ChatVo chatVo = chatService.selectChat(chat.getChatId()); //如果对方是移动端则按照移动端消息格式推送 TioUtil.sendChatMessageToUser(context.getGroupContext(),chat.getToId(),"5",chatVo); //还需要推送给客服自己 TioUtil.sendChatMessageToUser(context.getGroupContext(),chat.getFromId(),"5",messageBean); return null; }}
package com.dd.admin.business.webSocket.handler; import cn.hutool.core.bean.BeanUtil; import com.dd.admin.business.chat.domain.ChatVo; import com.dd.admin.business.chat.domain.MessageBean; import com.dd.admin.business.chat.entity.Chat; import com.dd.admin.business.chat.service.ChatService; import com.dd.admin.business.webSocket.MsgHandlerInterface; import com.dd.admin.business.webSocket.util.TioUtil; import com.dd.admin.common.utils.AddressUtils; import lombok.extern.slf4j.Slf4j; import org.checkerframework.checker.units.qual.C; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; import org.tio.core.ChannelContext; import java.util.Map; @Component @Slf4j @Service("6") public class ServiceMessageHandler implements MsgHandlerInterface { public static ServiceMessageHandler handler; @Autowired ChatService chatService; @Override public Object handler(Map map, ChannelContext context ){ System.out.println(map); MessageBean messageBean = BeanUtil.toBean(map, MessageBean.class); //xx发送人 MessageBean.FromUser fromUser = messageBean.getFromUser(); Chat chat = new Chat(); chat.setFromId(fromUser.getId()); chat.setFromName(fromUser.getDisplayName()); chat.setToId(messageBean.getToContactId()); chat.setContent(messageBean.getContent()); chat.setIpAddress(context.getClientNode().getIp()); chat.setIpRealAddress(AddressUtils.getRealAddress(chat.getIpAddress())); //ip真实地址 chatService.save(chat); ChatVo chatVo = chatService.selectChat(chat.getChatId()); //如果对方是移动端则按照移动端消息格式推送 TioUtil.sendChatMessageToUser(context.getGroupContext(),chat.getToId(),"5",chatVo); //还需要推送给客服自己 TioUtil.sendChatMessageToUser(context.getGroupContext(),chat.getFromId(),"6",messageBean); return null; }}

View File

@ -55,8 +55,11 @@ public class ApiInterceptor implements HandlerInterceptor {
request.setAttribute("authorId",authorId);
Author author = authorService.getById(authorId);
if(author==null){
throw new ApiException(700,"token已失效");
}
request.setAttribute("author",author);
if(author.getAuthorStatus().equals(1)){
if(author.getAuthorStatus()!=null&&author.getAuthorStatus().equals(1)){
throw new ApiException(700,"当前用户已冻结~");
}
return true;

View File

@ -39,9 +39,18 @@ export function deleteChat(copyId) {
})
}
export function getAuthorChatList() {
export function getAuthorChatList(params) {
return request({
url: '/admin/chat/authorList',
method: 'get'
method: 'get',
params
})
}
export function getAuthorChat(params) {
return request({
url: '/admin/chat/getAuthorChat',
method: 'get',
params
})
}

View File

@ -1,108 +1,121 @@
import { Message } from 'element-ui'
let socket = null;//实例对象
let socketLeaveFlag = false
let socketReconnectTimer = null // 计时器对象——重连
let socketReconnectLock = false // WebSocket重连的锁
const heartCheck = {
vueThis: this, // vue实例
timeout: 6000, // 超时时间
timeoutObj: null, // 计时器对象——向后端发送心跳检测
serverTimeoutObj: null, // 计时器对象——等待后端心跳检测的回复
// 心跳检测重置
reset: function () {
clearTimeout(this.timeoutObj);
clearTimeout(this.serverTimeoutObj);
return this;
},
// 心跳检测启动
start: function () {
this.timeoutObj && clearTimeout(this.timeoutObj);
this.serverTimeoutObj && clearTimeout(this.serverTimeoutObj);
this.timeoutObj = setTimeout(() => {
// 这里向后端发送一个心跳检测,后端收到后,会返回一个心跳回复
socket.send("ping");
console.log("发送心跳检测");
this.serverTimeoutObj = setTimeout(() => {
// 如果超过一定时间还没重置计时器说明websocket与后端断开了
console.log("未收到心跳检测回复");
// 关闭WebSocket
socket.close();
}, this.timeout);
}, this.timeout);
},
}
const initWebSocket = (wsUrl) => {
return new Promise(function (resolve, reject) {
if ("WebSocket" in window) {
socket = new WebSocket(wsUrl);
socket.onerror = webSocketOnError;
socket.onmessage = webSocketOnMessage;
socket.onclose = webSocketOnClose;
socket.onopen = () => {
console.log('连接成功');
heartCheck.reset().start();
resolve(socket)
class WebSocketManager {
constructor() {
this.webSocketInstance = null; // WebSocket实例对象使用更清晰的命名
this.reconnectTimer = null; // 断线重连后,存储延迟请求的代码,用于延迟重连
this.isConnected = false; // 连接标识,使用更符合语义的命名
this.onConnectCallback = null; // 连接成功的回调函数
this.onMessageCallback = null; // 收到消息的回调函数
this.onCloseCallback = null; // 连接关闭的回调函数
this.heartbeatCheck = {
interval: 5000, // 每段时间发送一次心跳包的时间间隔这里设置为5秒使用更表意明确的变量名
timer: null, // 延时发送消息对象(启动心跳新建这个对象,收到消息后重置对象)
start: () => {
if (this.isConnected) {
console.log('发送WebSocket心跳');
this.webSocketInstance.send('ping');
}
},
reset: () => {
clearTimeout(this.heartbeatCheck.timer);
this.heartbeatCheck.timer = setTimeout(() => {
this.heartbeatCheck.start();
}, this.heartbeatCheck.interval);
}
};
}
// 连接WebSocket的方法
connect(url) {
try {
if (this.isConnected) {
return;
}
console.log("连接WebSocket");
this.webSocketInstance = new WebSocket(url);
this.webSocketInstance.onmessage = (event) => {
const data = event.data;
if (data === 'pong') {
// 重新开启心跳定时
this.heartbeatCheck.reset();
} else {
// 其他消息转发出去
console.log("收到消息:", data);
this.onMessageCallback && this.onMessageCallback(JSON.parse(data));
}
};
} else {
Message({
message: "您的浏览器不支持websocket请更换Chrome或者Firefox",
type: 'error',
})
return reject(new Error('浏览器不支持websocket'));
this.webSocketInstance.onclose = (event) => {
console.log('WebSocket连接关闭');
this.isConnected = false;
this.onCloseCallback && this.onCloseCallback(event);
};
this.webSocketInstance.onopen = () => {
console.log("WebSocket连接成功");
this.isConnected = true;
this.heartbeatCheck.start();
};
// 连接发生错误的回调方法
this.webSocketInstance.onerror = () => {
console.log('WebSocket连接发生错误');
this.isConnected = false;
this.reconnect(url);
};
} catch (error) {
console.log("尝试创建连接失败");
this.reconnect(url);
}
});
}
const webSocketOnError = (e) => {
console.log("WebSocket:发生错误");
socketReconnect(e.target.url)
window.dispatchEvent(
new CustomEvent("WSOnError", e)
);
}
//服务器返回的数据
const webSocketOnMessage = (e) => {
if (e.data === 'pong') {
console.log('收到心跳回复');
heartCheck.reset().start();
} else {
console.log('服务器返回的数据')
console.log(e)
}
// 定义重连函数
reconnect(url) {
console.log("尝试重新连接");
if (this.isConnected) {
return;
}
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer);
}
this.reconnectTimer = setTimeout(() => {
this.connect(url);
}, 15000);
}
// 设置关闭连接的方法
close(code) {
this.webSocketInstance && this.webSocketInstance.close(code);
}
// 发送消息的方法
sendMessage(messageData) {
if (this.webSocketInstance && this.webSocketInstance.readyState === WebSocket.OPEN) {
// 若是ws开启状态
this.webSocketInstance.send(messageData);
} else if (this.webSocketInstance && this.webSocketInstance.readyState === WebSocket.CONNECTING) {
setTimeout(() => {
this.sendMessage(messageData);
}, 1000);
} else {
setTimeout(() => {
this.sendMessage(messageData);
}, 1000);
}
}
// 设置连接成功回调函数的方法
onConnect(callback) {
this.onConnectCallback = callback;
}
// 设置收到消息回调函数的方法
onMessage(callback) {
this.onMessageCallback = callback;
}
// 设置连接关闭回调函数的方法
onClose(callback) {
this.onCloseCallback = callback;
}
}
const webSocketOnClose = (e) => {
console.log("WebSocket:已关闭");
// 清除心跳定时器
heartCheck.reset();
if (!socketLeaveFlag) {
// websocket重连
socketReconnect(e.target.url)
}
}
const sendMessage = (data) =>{
if (socket) {
socket.send(data);
console.log('消息已发送:', data);
}
}
const socketReconnect = (url) => {
if (socketReconnectLock) {
return;
}
socketReconnectLock = true;
socketReconnectTimer && clearTimeout(socketReconnectTimer);
socketReconnectTimer = setTimeout(() => {
console.log("WebSocket:重连中...");
socketReconnectLock = false;
// websocket启动
initWebSocket(url);
}, 4000);
}
const closeWebSocket = () => {
socketLeaveFlag = true
if (socket) {
console.log('断开连接');
socket.close();
}
}
//具体问题具体分析,把需要用到的方法暴露出去
export default { closeWebSocket, socket, initWebSocket,sendMessage };
const webSocketManager = new WebSocketManager();
export default webSocketManager;

View File

@ -24,9 +24,9 @@
</template>
<script>
import {getAuthorChatList} from "@/api/business/chat/chat";
import {getAuthorChatList,getAuthorChat} from "@/api/business/chat/chat";
import {isNotEmpty} from "@/utils";
import Websocket from "@/api/websocket.js";
import webSocketManager from "@/api/websocket.js";
export default {
name: "Im",
@ -37,13 +37,37 @@
return {
dialogVisible: false,
temp:{},
page:1,
user:{id:8,displayName:'官方客服-薯队长',avatar:'http://8.146.211.120:8080/upload/avatar/kefu.jpg'},
}
},
mounted(){
Websocket.initWebSocket('ws://192.168.1.136:9326/?authorId=8').then(response=>{
console.log(response.onmessage = this.handleMessage)
})
webSocketManager.connect('ws://192.168.10.98:9326/?authorId=8')
//
webSocketManager.onMessage((data) => {
if(data.handlerType == '6'){
console.log('mounted')
console.log(JSON.stringify(data.body))
const {IMUI} = this.$refs;
IMUI.appendMessage({
"content": "1",
"fromUser": {
"avatar": "http://8.146.211.120:8080/upload/avatar/avatar (3).jpg",
"displayName": "复古喵星人15",
"id": "8"
},
"handlerType": 0,
"id": "c4d344c83fe0e86e23b7508b73e0b5ef",
"sendTime": 1736439914000,
"status": "succeed",
"toContactId": "8",
"type": "text"
});
}
});
},
methods: {
@ -56,29 +80,29 @@
this.getAuthorList()
},
getAuthorList(){
getAuthorChatList().then(response=> {
getAuthorChatList({authorId:8}).then(response=> {
this.$nextTick(() => {
const {IMUI} = this.$refs;
const authorList = response.data;
const contacts = [];
for (let item of authorList) {
let data = {
id: item.id,
displayName: item.displayName,
avatar: item.avatar,
index: item.index,
unread: item.unread,
//
//lastContentRender file '[]', image '[]' text img,
//
lastSendTime: item.lastSendTime,
const {IMUI} = this.$refs;
const authorList = response.data;
const contacts = [];
for (let item of authorList) {
let data = {
id: item.id,
displayName: item.displayName,
avatar: item.avatar,
index: item.index,
unread: item.unread,
//
//lastContentRender file '[]', image '[]' text img,
//
lastSendTime: item.lastSendTime,
}
if(isNotEmpty(item.lastContent)){
data.lastContent = IMUI.lastContentRender({type: 'text', content: item.lastContent})
}
contacts.push(data)
}
if(isNotEmpty(item.lastContent)){
data.lastContent = IMUI.lastContentRender({type: 'text', content: item.lastContent})
}
contacts.push(data)
}
IMUI.initContacts(contacts);
IMUI.initContacts(contacts);
})
})
},
@ -91,26 +115,19 @@
handleSend(message, next, file) {
console.log('发送了信息')
message.handlerType = '6'
Websocket.sendMessage(JSON.stringify(message))
webSocketManager.sendMessage(JSON.stringify(message))
//next next({status:'failed'});
next();
},
handlePullMessages(contact, next) {
console.log('拉取信息')
//
const messages = [{
id: '唯一消息ID',
status: 'succeed',
type: 'text',
sendTime: 1566047865417,
content: '你什么才能对接完?',
toContactId: contact.id,
fromUser:this.user
}]
console.log(contact)
getAuthorChat({authorId:8,fromId:contact.id,limit:6,page:this.page}).then(res=> {
console.log(res.data.records)
next(res.data.records.reverse(),true);
})
//true
next(messages,true);
},
},
}