diff --git a/src/main/java/com/dd/admin/business/chat/controller/ChatController.java b/src/main/java/com/dd/admin/business/chat/controller/ChatController.java index 04ef01e..6c61768 100644 --- a/src/main/java/com/dd/admin/business/chat/controller/ChatController.java +++ b/src/main/java/com/dd/admin/business/chat/controller/ChatController.java @@ -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> authorList() { - List authorChats = chatService.selectAuthorChatList(); + public ResultBean> authorList(String authorId) { + List 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> getAuthorChat(String authorId, String fromId) { + ChatDto chatDto = new ChatDto(); + chatDto.setFromId(fromId); + chatDto.setToId(authorId); + + IPage chatPage = chatService.selectChatPage(chatDto); + List records = chatPage.getRecords(); + List 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 = "-分页列表") diff --git a/src/main/java/com/dd/admin/business/chat/domain/AuthorChat.java b/src/main/java/com/dd/admin/business/chat/domain/AuthorChat.java index f432043..3acae3f 100644 --- a/src/main/java/com/dd/admin/business/chat/domain/AuthorChat.java +++ b/src/main/java/com/dd/admin/business/chat/domain/AuthorChat.java @@ -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; } diff --git a/src/main/java/com/dd/admin/business/chat/mapper/xml/ChatMapper.xml b/src/main/java/com/dd/admin/business/chat/mapper/xml/ChatMapper.xml index 0633da9..33465ec 100644 --- a/src/main/java/com/dd/admin/business/chat/mapper/xml/ChatMapper.xml +++ b/src/main/java/com/dd/admin/business/chat/mapper/xml/ChatMapper.xml @@ -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 ( diff --git a/src/main/java/com/dd/admin/business/webSocket/handler/P2PMessageHandler.java b/src/main/java/com/dd/admin/business/webSocket/handler/P2PMessageHandler.java index 9f95b48..94f8d9d 100644 --- a/src/main/java/com/dd/admin/business/webSocket/handler/P2PMessageHandler.java +++ b/src/main/java/com/dd/admin/business/webSocket/handler/P2PMessageHandler.java @@ -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; }} \ No newline at end of file +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; }} \ No newline at end of file diff --git a/src/main/java/com/dd/admin/business/webSocket/handler/ServiceMessageHandler.java b/src/main/java/com/dd/admin/business/webSocket/handler/ServiceMessageHandler.java index fde3442..f923a86 100644 --- a/src/main/java/com/dd/admin/business/webSocket/handler/ServiceMessageHandler.java +++ b/src/main/java/com/dd/admin/business/webSocket/handler/ServiceMessageHandler.java @@ -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; }} \ No newline at end of file +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; }} \ No newline at end of file diff --git a/src/main/java/com/dd/admin/common/security/interceptor/ApiInterceptor.java b/src/main/java/com/dd/admin/common/security/interceptor/ApiInterceptor.java index ec72399..8c9af94 100644 --- a/src/main/java/com/dd/admin/common/security/interceptor/ApiInterceptor.java +++ b/src/main/java/com/dd/admin/common/security/interceptor/ApiInterceptor.java @@ -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; diff --git a/web/src/api/business/chat/chat.js b/web/src/api/business/chat/chat.js index b808ddd..1ab7247 100644 --- a/web/src/api/business/chat/chat.js +++ b/web/src/api/business/chat/chat.js @@ -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 }) } diff --git a/web/src/api/websocket.js b/web/src/api/websocket.js index ca27a83..30b77b4 100644 --- a/web/src/api/websocket.js +++ b/web/src/api/websocket.js @@ -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; diff --git a/web/src/views/common/Im.vue b/web/src/views/common/Im.vue index 926231e..a649eef 100644 --- a/web/src/views/common/Im.vue +++ b/web/src/views/common/Im.vue @@ -24,9 +24,9 @@