利用websocket实现web端客服实时聊天系统

时间:2022-10-16 19:58:24

@​​TOC​

需求场景模拟

1.移动端给客服发送消息,客户在bs端后台收到消息并回复​​(本文以一个客服为例)​​ 2.左侧聊天栏显示最新的消息和消息时间 3.需要查看对方是否已读自己的消息

开发需求

一、技术选型

使用websocket进行消息推送 优点:个人感觉开发简单,不需要部署第三方服务 缺点:无状态,页面每刷新一次就会产生一个新的session,某些情况下不稳定 ​​还是那句话,技术没有什么好与坏,只有合适不合适,同时最终采用技术栈意味着你能​忍受它的缺点

二、需求分析

1.发送消息意味着需要发送人和接收人两个角色,同时需要对用户进行持久化 2.对接收人(发送人)来说,显示最新的消息和时间,就意味着显示双方消息记录的最后提条消息的内容和发送时间 3.消息已读意味着打开聊天对话框就要告诉对方,自己已读对方的消息。这里会产生两种情况: 己方在线对方未在线,需要在对方上线时(即打开对话框)告诉对方自己已读对方的消息 ​​解决方案​​:存储消息数据,在自己打开对框的时候,获取聊天记录,并将聊天记录中对方给自己发的消息状态全部更新为已读。

双方同时在线(聊天对话界面),这里稍微有点操作,那就是如何让双方及时的知道对方已读自己的消息。 ​​场景及解决方案​​: 场景:当自己给对方发送消息时,自己看到自己发给对方的消息已读(即便对方不给自己发消息)​解决​​: 1.自己对对方发消息时,更新对方发给自己的全部消息为已读,对方读取消息 2.对方读取消息时,让对方告诉自己对方已读自己的消息,自己进行已读展示,通过一个共用接 口即可,当对方调用指定接口时,让对方告诉自己对方已读即可。 4.利用mongodb进行用户以及聊天记录的存储

效果演示

消息聊天演示:

利用websocket实现web端客服实时聊天系统

消息时间演示:

利用websocket实现web端客服实时聊天系统

消息未读演示:

利用websocket实现web端客服实时聊天系统

软件需求实现

1.技术架构

PC端:vue+springboot
移动端:html+springboot

2.实现流程图:(​​仅供参考​​)

利用websocket实现web端客服实时聊天系统

一、数据库设计

1.sys_user

字段

说明

userId

用户id

nickName

昵称

heardUrl

头像地址

sex

性别

2.chat_msg

字段

说明

createTime

创建时间

msgKey

通信key(sendId_receiveId)

chatMsg

通信消息

sendId

发送id

receiveId

接收id

readState

查看状态 0未看 1已看

二、代码实现

说明:​代码有所删减修改,但核心代码存在!!!请选择性复用代码​

1.web端

引入依赖

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
<version>2.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-websocket</artifactId>
<version>9.0.39</version>
</dependency>

配置yml

#开发时服务端口号,部署无效
server:
port: 9019
servlet:
context-path: fmis
spring:
data:
mongodb:
host: 192.168.1.125
port: 27017
database: cangzhou
1.前端代码

index.vue

<template>
<div :class="classObj" class="app-wrapper">
<Logo></Logo>
<div class="clearfix"></div>
<sidebar class="sidebar-container" />
<div class="communicate" >
<el-button type="text" @click="talkRoomVisible = true" :class="{ breathing: unReadMessageNum && talkRoomVisible ==false}">
//对话图标
<img src="../../static/images/login/tips.png" alt />
</el-button>
</div>
<CommunicationInterface
style="
position: absolute;
z-index: 1000;
top: 0;
right: 0;
left: 0;
margin: auto;
"
v-if="talkRoomVisible"
:userList="userList"
/>
</div>
</template>

<script>
import message from "./mixin/message";
import CommunicationInterface from "@/dialog/chat.vue";
export default {
name: "Layout",
components: {
Navbar,
Sidebar,
AppMain,
Logo,
CommunicationInterface,
},
data() {
return {};
},
mixins: [ResizeMixin, message],
computed: {
sidebar() {
return this.$store.state.app.sidebar;
},
device() {
return this.$store.state.app.device;
},
fixedHeader() {
return this.$store.state.settings.fixedHeader;
},
classObj() {
return {
hideSidebar: !this.sidebar.opened,
openSidebar: this.sidebar.opened,
withoutAnimation: this.sidebar.withoutAnimation,
mobile: this.device === "mobile",
};
},
unreadList() {
return this.userList.filter((e) => {
return e.unReadMsgNum > 0;
});
},
},
methods: {
handleClickOutside() {
this.$store.dispatch("app/closeSideBar", { withoutAnimation: false });
},
},
};
</script>

<style lang="scss" scoped>
@import "~@/styles/mixin.scss";
@import "~@/styles/variables.scss";
.breathing {
animation: breathe 1s infinite;
}
@keyframes breathe {
0% {
opacity: 0.1;
}
50% {
opacity: 1;
}
100% {
opacity: 0.1;
}
}
.hoverVisibleUnreadList{
position: absolute;
bottom: 35px;
right: -50px;
width: 180px;
line-height: 35px;
display: none;
border-radius: 3px;
box-shadow: 0 0 2px #888888;
}
.communicate:hover .hoverVisibleUnreadList{
display: block
}
.app-wrapper {
@include clearfix;
position: relative;
height: 100%;
width: 100%;
&.mobile.openSidebar {
position: fixed;
top: 0;
}
}
.drawer-bg {
background: #000;
opacity: 0.3;
width: 100%;
top: 0;
height: 100%;
position: absolute;
z-index: 999;
}

.fixed-header {
position: fixed;
top: 0;
right: 0;
z-index: 9;
width: calc(100% - 0); // calc(100% - #{$sideBarWidth})
transition: width 0.28s;
}

.hideSidebar .fixed-header {
width: calc(100% - 0px);
}

.mobile .fixed-header {
width: 100%;
}
.communicate {
position: absolute;
z-index: 2000;
height: 50px;
width: 50px;
bottom: 50px;
right: 50px;
border-radius: 50%;
img {
width: 50px;
}
}
</style>
chat.vue
<template>
<div>
<div class="box">
<div class="min">
<ul class="contactList">
<h3 style="border-bottom: 0.5px solid #337bf1">联系人列表</h3>
<li
v-for="item in userListSortByLastTime"
:key="item.nickName"
@click="CreateConnection(item)"
:class="{ red: item.userId === userId }"
>
<div class="leftLi">
<!-- <img
:src="require('../../../static/images/login/titleboy.png')"
/> -->
<img :src="item.heardUrl" />
<div class="ListItem">
<div>
<div class="nickName">{{ item.nickName }}</div>
<span style="vertical-align: top">{{
item.lastMsgTime | dateFormat
}}</span>
</div>
<div class="ellipsis">
{{ item.lastMsg }}
<span v-if="item.unReadMsgNum > 0">{{
[item.unReadMsgNum]
}}</span>
</div>
</div>
<div class="delete" @click="delUserByUserId(item)">删除</div>
</div>
</li>
</ul>
<div class="chat">
<h3>{{ contactsName || "聊天区域" }}</h3>
<div class="content" ref="reference">
<ul style="color: black">
<li
v-for="item in content"
:key="item.id"
:style="item.flag ? 'text-align: right;' : 'text-align: left;'"
>
<span>
<!-- <img
v-show="!item.flag"
:src="require('../../../static/images/login/titleboy.png')"
/> -->
<img
v-show="!item.flag"
:src="heardUrl"
/>
<span class="times" v-show="item.flag">{{
item.createTime
? item.createTime.substr(11, item.createTime.length)
: ""
}}</span>
<span :class="item.flag ? 'i' : 'he'">{{ item.name }}</span>
<span class="time" v-show="!item.flag">{{
item.createTime
? item.createTime.substr(11, item.createTime.length)
: ""
}}</span>
<img
v-show="item.flag"
:src="require('../../../static/images/login/titleboy.png')"
/>
<!-- <img v-show="item.flag" :src="heardUrl" /> -->
<div class="readOrUnread" v-if="item.flag">
{{ item.readState == "1" ? "已读" : "未读" }}
</div>
</span>
</li>
</ul>
</div>
<div class="input">
<textarea
cols="50"
rows="10"
v-model="text"
@keydown.enter="submit"
></textarea>
</div>
</div>
</div>
</div>
</div>
</template>

<script>
// import axios from 'axios'
import * as API from "@/api/systemApplication";
import { dateFormat } from "@/utils/index";

export default {
props: {
dialogVisible: {
type: Boolean,
default: false,
},
userList: {
type: Array,
required: true,
},
},
data() {
return {
// dialogVisible: true
c: null,
text: null, //输入的消息
content: [], //内容列表
scoket: "",
sendId: 1,
userId: "",
contactsName: "",
URL: window.ROOT,
heardUrl: "", //右侧聊天框头像
};
},
computed: {
userListSortByLastTime() {
// 做排序,最新接收到的消息放在列表的最上面,需要后端提供年月日时分秒
return this.userList.sort((a, b) => {
let at = a.lastMsgTime ? `${a.lastMsgTime}` : "1970-01-01 00:00:00";
// console.log(at,"at")
let bt = b.lastMsgTime ? `${b.lastMsgTime}` : "1970-01-01 00:00:00";
// console.log(bt,"bt")
return new Date(bt).getTime() - new Date(at).getTime();
});
},
},
mounted() {
// 因为上面用了v-if 所以每次这个组件出来 都会走mounted
this.userId = this.userList[0].userId;
this.heardUrl = this.userList[0].heardUrl;
// 获取当前聊天人的消息
this.getSelectedUserMessageList();
// 接收ws消息
this.$root.$on("pushNewMessage", this.pushNewMessage);

this.$root.$emit("changeUnReadMsgNum", {
userId: this.userId,
});
},
destroyed() {
// 取消接收ws消息
this.$root.$off("pushNewMessage", this.pushNewMessage);
},
filters: {
dateFormat(v) {
if (!v) {
return;
}
// 如果是昨天发的消息,左侧列表中展示的时间只显示月、日
if (v.substr(0, 10) !== dateFormat(new Date()).substr(0, 10)) {
return dateFormat(v, "MM-DD");
}
// 如果是今天发的消息,左侧列表中展示的时间显示时、分
return dateFormat(v, "HH:mm");
},
},
methods: {
delUserByUserId(item) {
this.$root.$emit("loadUserList", {
userId:item.userId,
});
},
// 获取历史记录
getSelectedUserMessageList() {
API["storeHouseChatMsgHistory"]({
sendId: `${this.sendId}`,
receiveId: this.userId,
pageSize: "100",
currentPage: "1",
}).then((res) => {
const { data } = res;
this.content = data.list.reverse();
this.scrollAuto();
});
},
pushNewMessage(news) {
if (news.code == 0) {
console.log(11112, news);
for (var i = 0; i < this.content.length; i++) {
this.content[i].readState = 1;
}
return;
}
// 接收到的新消息
if (news.sendId != this.userId) return; // 这里判断id对不对再推数据
this.content.push({
flag: 0,
name: news.chatMsg,
readState: "0",
});
this.scrollAuto();
// 这里是为了做消除用户列表中的未读,也就是未读变为已读,告诉后端消息已读
API["msgRead"]({
sendId: `${this.sendId}`,
receiveId: `${this.userId}`,
});
// 更改前端页面上的数据,不再提示几条未读
this.$root.$emit("changeUnReadMsgNum", {
userId: news.sendId,
});

// if(news.code ==0){
// for(var i=0;i<this.content.length;i++){
// this.content[i].readState=1
// }
// }
},
// 按enter键(也就是要发送消息)
submit() {
if (!this.text) {
return;
}
this.content.push({
flag: 1,
name: this.text,
readState: "0",
});
// 修改左侧列表上展示的最后一条消息和时间
this.$root.$emit("changeLast", {
lastMsg: this.text,
lastMsgTime: dateFormat(Date.now()),
userId: this.userId,
});
this.$root.$talkRoomWs.send(
JSON.stringify({
linkType: "msg",
sendId: `${this.sendId}`,
userId: this.sendId,
receiveId: this.userId,
msgKey: `${this.sendId}_${this.userId}`,
chatMsg: this.text,
readState: "0",
})
);
this.text = null;
this.scrollAuto();
},
// 这里是点击左侧成员列表时执行的函数
CreateConnection(item) {
if (this.userId === item.userId) return;
this.contactsName = item.nickName;
this.text = "";
this.content = [];
this.userId = item.userId;
this.getSelectedUserMessageList();
this.$root.$emit("changeUnReadMsgNum", item);
API["msgRead"]({
sendId: `${this.sendId}`,
receiveId: `${this.userId}`,
});
// 点击左侧列表时,聊天框中的头像也要切换
for (var i = 0; i < this.userList.length; i++) {
if (this.userList[i].userId == item.userId) {
this.heardUrl = this.userList[i].heardUrl;
}
}
},
scrollAuto() {
this.$nextTick(() => {
this.$refs["reference"].scrollTop =
this.$refs["reference"].scrollHeight;
});

},
},
};
</script>

<style lang="scss" scoped>
h3 {
text-align: center;
height: 40px;
line-height: 40px;
border-bottom: 0.5px solid #ccc;
font-size: 16px;
font-weight: 300;
}
.box {
margin-top: 150px;
margin-left: 28%;
width: 900px;
border-radius: 10px;
overflow: hidden;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.04);
.min {
display: flex;
.contactList {
list-style: none;
overflow: auto;
height: 600px;
width: 260px;
background: #4387f6;
color: azure;
padding: 0px 5px;
padding-left: 5px;

.red {
background: #5d9aff;
border-radius: 10px;
}

li {
cursor: pointer;
text-align: left;
box-sizing: border-box;
padding: 14px 0px;
padding-left: 5px;
height: 65px;
}

.leftLi {
@extend .min;
img {
height: 40px;
display: block;
}
.ListItem {
@extend .min;
padding-left: 5px;
flex-direction: column;
}

div :first-child {
height: 30px;
line-height: 20px;
span {
color: #bed7ff;
font-size: 12px;
margin-left: 15px;
}
}

div :last-child {
line-height: 0px;
color: #bed7ff;
font-size: 12px;
span {
text-align: center;
border-radius: 3px;
}
}
}
}
.chat {
background: #f3f3f3;
width: 700px;
height: 600px;
@extend .min;
flex-direction: column;
.content {
overflow: auto;
height: 500px;
span {
display: inline-block;
padding: 0px 5px;
line-height: 28px;
@mixin name($px) {
transition: 0.5s;
opacity: 0;
transform: translate($px);
}
.time {
@include name(-20px);
}
.times {
@include name(20px);
}
}

span:hover .time {
opacity: 1;
transform: translate(0px);
}
span:hover .times {
opacity: 1;
transform: translate(0px);
}

@mixin Bubble($color) {
background: $color;
border-radius: 5px;
border-bottom-left-radius: 20px;
}

.i {
color: #f3f3f3;
@include Bubble(#5d9aff);
max-width: 65%;
text-align: left;
word-wrap: break-word;
word-break: break-all;
overflow: hidden;
padding: 8px 5px;
}
.he {
@include Bubble(#e9e9e9);
max-width: 65%;
word-wrap: break-word;
word-break: break-all;
overflow: hidden;
padding: 8px 5px;
}
}
.input {
height: 180px;
border-top: 0.5px solid #f3f3f3;
textarea {
width: 100%;
border: 0px;
resize: none;
}
}
}
}
}

ul > li {
width: 100px;
text-align: center;
width: 100%;
margin-top: 3px;
}

img {
height: 45px;
vertical-align: top;
border-radius: 50%;
}
.content li > span {
position: relative;
}
.content li {
margin-top: 15px;
}
.readOrUnread {
font-size: 12px;
margin-right: 47px;
line-height: 12px;
}
.ellipsis {
width: 100px;
height: 12px;
line-height: 12px !important;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.nickName {
display: inline-block;
width: 90px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.leftLi {
position: relative;
}
.delete {
position: absolute;
bottom: 3px;
right: 45px;
color: #fff;
}
</style>

message.js

import * as API from "@/api/systemApplication";
export default {
data() {
return {
sendId: 1, // todo 客服id,默认为1
scoket: null,
userList: [], //联系人列表
talkRoomVisible: false,
}
},
computed: {
// 未读消息的数量
unReadMessageNum() {
let unReadNum = 0;
for (let i = 0; i < this.userList.length; i++) {
const e = this.userList[i];
// 注意这里的unReadMsgNum可能不是数字
if (e.unReadMsgNum && typeof e.unReadMsgNum == 'number') {
unReadNum += e.unReadMsgNum
}
}
return unReadNum
}
},
mounted() {
// 初始化ws
this.initWs();
// 获取左侧用户列表
this.getList();
// 如果聊天窗口是开启状态,按esc键,则关闭聊天窗口
this.initCloseTalkRoom()
// 修改前端页面上左侧列表展示的未读条数
this.$root.$on("changeUnReadMsgNum", this.changeUnReadMsgNum);
// 修改左侧列表上展示的最后一条消息和时间
this.$root.$on("changeLast", this.changeLast);
// 删除聊天室左侧列表中的项,再重新加载用户列表
this.$root.$on("loadUserList", this.loadUserList);
},
methods: {
initWs() {
// let url = this.URL;
// this.$root.$talkRoomWs = new WebSocket("ws://192.168.1.169:9019/fmis-api/ws/asset");
let url = window.ROOT;
this.$root.$talkRoomWs = new WebSocket(`${url.replace("http", "ws")}/ws/asset`);
let ws = this.$root.$talkRoomWs;
ws.onopen = (e) => {
console.log(`WebSocket 连接状态: ${ws.readyState}`);
};
ws.onmessage = (data) => {
try {
let news = JSON.parse(data.data)
// 用新信息覆盖旧消息
if (news.unReadNumLst) {
for (let i = 0; i < news.unReadNumLst.length; i++) {
const e = news.unReadNumLst[i];
let index = this.userList.findIndex(item => item.userId == e.userId)
if (index > -1) {
// this.userList[index].lastMsg = e.lastMsg
// this.userList[index].lastMsgTime = e.lastMsgTime
// this.userList[index].unReadMsgNum = e.unReadMsgNum
Object.assign(this.userList[index], e)
} else {
// todo 新增的逻辑
this.userList.push(e)
}
}
}

this.$root.$emit('pushNewMessage', news)

} catch (err) {
// console.log(err);
}
};

ws.onclose = (data) => {
console.log("WebSocket连接已关闭");
};
setTimeout(() => {
ws.send(
JSON.stringify({
linkType: "bind",
sendId: this.sendId,
})
);
}, 500);
},
// 获取左侧的用户列表
getList() {
API["storeHouseWsUserList"]({
sendId: this.sendId,
userId: this.sendId,
}).then((res) => {
const { data } = res;
this.userList = data;
});
},
// 如果聊天窗口是开启状态,按esc键,则关闭聊天窗口
initCloseTalkRoom() {
window["onkeydown"] = (e) => {
if (e.keyCode === 27 && this.talkRoomVisible) {
this.talkRoomVisible = false
}
};
},
// 修改前端页面上左侧列表展示的未读条数
changeUnReadMsgNum(item) {
let index = this.userList.findIndex((e) => e.userId == item.userId);
console.log(index);
if (index > -1) {
this.userList[index].unReadMsgNum = 0;
}
console.log(this.userList[index].unReadMsgNum);
},
// 修改左侧列表上展示的最后一条消息和时间
changeLast(obj) {
this.userList.find((item) => {
if (item.userId === obj.userId) {
item.lastMsg = obj.lastMsg;
item.lastMsgTime = obj.lastMsgTime;
}
});
},
loadUserList(item) {
console.log(item,"item")
let postData= {
userId:item.userId
}
API["delUserByUserId"](postData).then((res) => {
console.log(res);
if (res.code) {
this.$message({
message: res.message,
type: "success",
});
this.getList()
}
});
}
}
}

systemApplication.js

// 获取列表
export function storeHouseWsUserList(data) {
return request({
url: '/chat/userList',
method: 'post',
data
})
}
// 获取历史连天记录
export function storeHouseChatMsgHistory(data) {
return request({
url: '/chat/msgHistory',
method: 'post',
data
})
}
// 已读未读
export function msgRead(data) {
return request({
url: '/chat/msgRead',
method: 'post',
data
})
}

// 删除左侧列表中的项

export function delUserByUserId(data) {
return request({
url: '/chat/delUserByUserId',
method: 'post',
data
})
}
2.后端代码

WebSocketConfig

@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter () {
return new ServerEndpointExporter();
}
}

WebSocketServer

import com.alibaba.fastjson.JSONObject;
import com.jinmdz.fmis.api.api.model.storehouse.chat.ChatMsg;
import com.jinmdz.fmis.api.global.CacheManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;

/**
* @author lvyq
* @version 1.0
* @description: TODO
* @date 2021/8/17 16:30
*/
@ServerEndpoint(value = "/ws/asset")
@Component
public class WebSocketServer {

private static ChatService chatService;
@Autowired
public void setChatService(ChatService chatService) {
WebSocketServer.chatService = chatService;
}

@PostConstruct
public void init() {
System.out.println("websocket 加载");
}
private static Logger log = LoggerFactory.getLogger(WebSocketServer.class);
private static final AtomicInteger OnlineCount = new AtomicInteger(0);
// concurrent包的线程安全Set,用来存放每个客户端对应的Session对象。
private static CopyOnWriteArraySet<Session> SessionSet = new CopyOnWriteArraySet<Session>();
/**
* 连接建立成功调用的方法
*/
@OnOpen
public void onOpen(Session session) {
SessionSet.add(session);
int cnt = OnlineCount.incrementAndGet();
log.info("有连接加入,当前连接数为:{}", cnt);
SendMessage(session, "连接成功");
}

/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose(Session session) {
SessionSet.remove(session);
int cnt = OnlineCount.decrementAndGet();
log.info("有连接关闭,当前连接数为:{}", cnt);
}

/**
* 收到客户端消息后调用的方法
* @param message 客户端发送过来的消息
*/
@OnMessage
public void onMessage(String message, Session session) {
log.info("来自客户端的消息:{}",message);
try{
JSONObject jsonObject = JSONObject.parseObject(message);
String linkType = jsonObject.getString("linkType");
String sendId = jsonObject.getString("sendId");
if (linkType.equals("bind")){
CacheManager.set("bind_"+sendId,session);
SendMessage(session, "连接成功");
}else if (linkType.equals("msg")){
//聊天
ChatMsg msg = new ChatMsg();
//发消息
String chatMsg = jsonObject.getString("chatMsg");
String receiveId = jsonObject.getString("receiveId");
msg.setChatMsg(chatMsg);
msg.setSendId(sendId);
msg.setMsgKey(sendId+"_"+receiveId);
msg.setReceiveId(receiveId);
msg.setReadState("0");
chatService.sendOne(msg);}
}catch (Exception e){
e.printStackTrace();
}
}

/**
* 出现错误
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("发生错误:{},Session ID: {}",error.getMessage(),session.getId());
error.printStackTrace();
}

/**
* 发送消息,实践表明,每次浏览器刷新,session会发生变化。
* @param session
* @param message
*/
public static void SendMessage(Session session, String message) {
try {
//session.getBasicRemote().sendText(String.format("%s (From Server,Session ID=%s)",message,session.getId()));
log.info("sessionID="+session.getId());
session.getBasicRemote().sendText(message);
} catch (IOException e) {
log.error("发送消息出错:{}", e.getMessage());
e.printStackTrace();
}
}


/**
* 指定Session发送消息
* @param sessionId
* @param message
* @throws IOException
*/
public static void SendMessageById(String message,String sessionId) throws IOException {
Session session = null;
for (Session s : SessionSet) {
if(s.getId().equals(sessionId)){
session = s;
break;
}
}
if(session!=null){
SendMessage(session, message);
}
else{
log.warn("没有找到你指定ID的会话:{}",sessionId);
}
}
/**
* @description: 指定发送
* @author: lvyq
* @date: 2021/9/24 11:30
* @version 1.0
*/
public static void SendMessageByRecId(String message,String receiveId) throws IOException {
Session session= CacheManager.get("bind_"+receiveId);
String sessionId = "";
if (session!=null){
sessionId=session.getId();
}
for (Session s : SessionSet) {
if(s.getId().equals(sessionId)){
session = s;
break;
}
}
if(session!=null){
SendMessage(session, message);
}
else{
log.warn("没有找到你指定ID的会话:{}",sessionId);
}
}

ChatController

import com.jinmdz.fmis.api.api.model.storehouse.chat.ChatMsg;
import com.jinmdz.fmis.api.api.model.storehouse.chat.SysUser;
import com.jinmdz.fmis.api.api.service.ChatService;
import com.jinmdz.fmis.api.api.service.WebSocketServer;
import com.jinmdz.fmis.api.base.BaseController;
import com.jinmdz.fmis.core.base.BaseResult;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.io.IOException;

/**
* @author lvyq
* @version 1.0
* @description: TODO-消息通信
* @date 2021/9/26 14:50
*/
@RestController
@RequestMapping("/chat")
public class ChatController extends BaseController {

@Resource
private ChatService chatService;

@Resource
private WebSocketServer webSocketServer;

/**
* @description: 消息记录
* @author: lvyq
* @date: 2021/9/26 15:19
* @version 1.0
*/
@PostMapping("/msgHistory")
public BaseResult msgHistory(@RequestBody ChatMsg data) throws IOException {
return chatService.msgHistory(data);
}

/**
* @description: 消息已读
* @author: lvyq
* @date: 2021/9/26 16:27
* @version 1.0
*/
@PostMapping("/msgRead")
private BaseResult msgRead(@RequestBody ChatMsg data) throws IOException {
return chatService.msgRead(data);
}

/**
* @description: 全体发送
* @author: lvyq
* @date: 2021/11/11 13:38
* @version 1.0
*/
@GetMapping("/sendAll/{msg}")
public void sendAll(@PathVariable("msg") String msg) throws IOException {
WebSocketServer.BroadCastInfo(msg);
}

/**
* @description: 发消息给某人
* @author: lvyq
* @date: 2021/9/24 11:15
* @version 1.0
*/
@PostMapping("/sendOne")
public void sendOne(@RequestBody ChatMsg data) throws IOException {
chatService.sendOne(data);
}

/**
* @description: 获取用户列表
* @author: lvyq
* @date: 2021/9/24 13:43
* @version 1.0
*/

@PostMapping("/userList")
private BaseResult userList(@RequestBody SysUser data){
return chatService.userListDev(data);
}

/**
* @description: 根据userId删除用户
* @author: lvyq
* @date: 2021/11/19 13:29
* @version 1.0
*/
@PostMapping("/delUserByUserId")
public BaseResult delUserByUserId(@RequestBody SysUser data){
return chatService.delUserByUserId(data.getUserId());
}

}

ChatService

import com.alibaba.fastjson.JSONObject;
import com.jinmdz.fmis.api.api.model.storehouse.chat.ChatMsg;
import com.jinmdz.fmis.api.api.model.storehouse.chat.SysUser;
import com.jinmdz.fmis.api.api.service.repository.SysUserRepository;
import com.jinmdz.fmis.api.base.BaseService;
import com.jinmdz.fmis.api.model.system.UserItem;
import com.jinmdz.fmis.core.base.BaseResult;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* @author lvyq
* @version 1.0
* @description: TODO
* @date 2021/9/24 10:06
*/
@Service
public class ChatService extends BaseService {

@Resource
private MongoTemplate mongoTemplate;

/**
* @description: 获取用户列表
* @author: lvyq
* @date: 2021/9/24 10:12
* @version 1.0
*/

public BaseResult userList(UserItem userItem, SysUser data) {
String receiveId = userItem.getId().toString();
List<SysUser> sysUserList = userLst(receiveId);
return successData(sysUserList);
}

/**
* @description: 发消息给某人
* @author: lvyq
* @date: 2021/9/24 11:16
* @version 1.0
*/
public void sendOne(ChatMsg data) throws IOException {
mongoTemplate.save(data);
//TODO 更新消息 接收者_发送者
String msg_key = data.getReceiveId()+"_"+data.getSendId();
String receiveId = data.getReceiveId();
HashMap<String,Object> map = new HashMap<>();
JSONObject mav = new JSONObject();
Query queryMsg = new Query().addCriteria(Criteria.where("readState").is("0").and("msgKey").is(msg_key));
Update update = new Update().set("readState","1");
//批量修改
mongoTemplate.updateMulti(queryMsg,update,ChatMsg.class,"chat_msg");
mav.put("sendId",data.getSendId());
mav.put("chatMsg",data.getChatMsg());
mav.put("unReadNumLst",userLst(receiveId));
WebSocketServer.SendMessageByRecId(mav.toJSONString(),receiveId);
}


/**
* @description: 消息列表信息-unLogin
* @author: lvyq
* @date: 2021/9/26 17:50
* @version 1.0
*/
public BaseResult userListDev(SysUser data) {
String receiveId = data.getUserId();
List<SysUser> sysUserList = userLst(receiveId);
return successData(sysUserList);
}

/**
* @description: 消息记录
* @author: lvyq
* @date: 2021/9/26 15:20
* @version 1.0
*/

public BaseResult msgHistory(ChatMsg data) throws IOException {
int page = data.getCurrentPage();
if (page<0){
page=1;
}
page=page-1;
int size = data.getPageSize();
if (size<0){
size=20;
}
if (StringUtils.isNotEmpty(data.getSendId()) && StringUtils.isNotEmpty(data.getReceiveId())){
String msgKeyOne= data.getSendId()+"_"+data.getReceiveId();
String msgKeyTwo = data.getReceiveId()+"_"+data.getSendId();
Criteria orCri1 = new Criteria();
Criteria orCri2 = new Criteria();
orCri1.andOperator(Criteria.where("msgKey").is(msgKeyOne));
orCri2.andOperator(Criteria.where("msgKey").is(msgKeyTwo));
Query query = new Query().addCriteria(new Criteria().orOperator(orCri1,orCri2));
long total = mongoTemplate.count(query,ChatMsg.class);
Pageable pageable = PageRequest.of(page,size, Sort.by(Sort.Direction.DESC,"createTime"));
query.with(pageable);
List<ChatMsg> list = mongoTemplate.find(query,ChatMsg.class);
HashMap<String, Object> map = new HashMap<>();
if (list != null) {
for (ChatMsg chatMsg:list){
chatMsg.setName(chatMsg.getChatMsg());
if (chatMsg.getSendId().equals(data.getSendId())){
chatMsg.setFlag(1);
}else {
chatMsg.setFlag(0);
}
}
Map<String, Object> pageMap = new HashMap<>();
map.put("list", list);
pageMap.put("total", total);
pageMap.put("pageSize", data.getPageSize());
pageMap.put("currentPage", data.getCurrentPage());
map.put("pager", pageMap);
}
Query queryMsg = new Query().addCriteria(Criteria.where("readState").is("0").and("sendId").is(data.getReceiveId()).and("receiveId").is(data.getSendId()));
Update update = new Update().set("readState","1");
mongoTemplate.updateMulti(queryMsg,update,ChatMsg.class,"chat_msg");
//消息已读通知
/*JSONObject mav = new JSONObject();
mav.put("state",true);
mav.put("msg","消息已读");
WebSocketServer.SendMessageByRecId(mav.toJSONString(),data.getReceiveId());*/
return successData(map);
}else {
return failure("非法参数");
}

}

/**
* @description: 将消息修改为已读
* @author: lvyq
* @date: 2021/9/26 16:27
* @version 1.0
*/

public BaseResult msgRead(ChatMsg data) throws IOException {
Query query = new Query().addCriteria(Criteria.where("readState").is("0").and("sendId").is(data.getReceiveId()).and("receiveId").is(data.getSendId()));
Update update = new Update().set("readState","1");
mongoTemplate.updateMulti(query,update,ChatMsg.class,"chat_msg");
//消息已读通知
JSONObject mav = new JSONObject();
mav.put("state",true);
mav.put("msg","消息已读");
mav.put("code",0);
WebSocketServer.SendMessageByRecId(mav.toJSONString(),data.getReceiveId());
return success("操作成功");

}

/**
* @description: 获取集合
* @author: lvyq
* @date: 2021/9/26 17:46
* @version 1.0
*/
private List<SysUser> userLst(String receiveId){
List<SysUser> sysUserList = mongoTemplate.findAll(SysUser.class);
for (SysUser sysUser:sysUserList){
String sendId = sysUser.getUserId();
String msgKey = sendId+"_"+receiveId;
if (sendId.equals(receiveId)){
continue;
}
//最新一条记录
String msgKeyOne= sendId+"_"+receiveId;
String msgKeyTwo = receiveId+"_"+sendId;
Criteria orCri1 = new Criteria();
Criteria orCri2 = new Criteria();
orCri1.andOperator(Criteria.where("msgKey").is(msgKeyOne));
orCri2.andOperator(Criteria.where("msgKey").is(msgKeyTwo));
Query msgQuery = new Query().addCriteria(new Criteria().orOperator(orCri1,orCri2));
Pageable pageable = PageRequest.of(0,1, Sort.by(Sort.Direction.DESC,"createTime"));
msgQuery.with(pageable);
List<ChatMsg> list = mongoTemplate.find(msgQuery,ChatMsg.class);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
if (list.size()>0){
sysUser.setLastMsg(list.get(0).getChatMsg());
sysUser.setLastMsgTime(simpleDateFormat.format(list.get(0).getCreateTime()));
}else {
sysUser.setLastMsg("");
sysUser.setLastMsgTime("");
}
Query query = new Query().addCriteria(Criteria.where("readState").is("0").and("msgKey").is(msgKey));
long unReadNum = mongoTemplate.count(query, ChatMsg.class);
sysUser.setUnReadMsgNum(unReadNum);
}
return sysUserList;
}

/**
* @description:时间转换
* @author: lvyq
* @date: 2021/9/27 16:48
* @version 1.0
*/
private String changeTime(Date createTime) {
Date date = new Date();
SimpleDateFormat formatDay = new SimpleDateFormat("MM-dd");
if (formatDay.format(date).equals(formatDay.format(createTime))){
SimpleDateFormat formatMin = new SimpleDateFormat("HH:mm");
return formatMin.format(createTime);
}else {
return formatDay.format(createTime);
}

}

/**
* @description: 根据userId删除用户
* @author: lvyq
* @date: 2021/11/19 13:29
* @version 1.0
*/

public BaseResult delUserByUserId(String userId) {
Query query = new Query().addCriteria(Criteria.where("userId").is(userId));
mongoTemplate.findAndRemove(query,SysUser.class);
return success("操作成功");
}
}

ChatMsg

import com.fasterxml.jackson.annotation.JsonFormat;
import com.jinmdz.fmis.core.base.BasePageData;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Transient;
import org.springframework.data.mongodb.core.mapping.Document;

import java.util.Date;

/**
* @author lvyq
* @version 1.0
* @description: 消息通信
* @date 2021/9/24 9:00
*/
@Data
@Document("chat_msg")
public class ChatMsg extends BasePageData {
@Id
private String id;

/**
* 创建时间
*/
@JsonFormat(pattern = yyyy_MM_dd_HH_mm_ss)
private Date createTime=new Date();

/**
* 通信key(sendId_receiveId)
*/
private String msgKey;

/**
* 通信消息
*/
private String chatMsg;

/**
* 发送id
*/
private String sendId;

/**
* 接收id
*/
private String receiveId;

/**
* 查看状态 0未看 1已看
*/
private String readState;

/**
*1为我 0 为他
*/
@Transient
private Integer flag;

@Transient
private String name;
}

SysUser

import com.jinmdz.fmis.core.base.BasePageData;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Transient;
import org.springframework.data.mongodb.core.mapping.Document;

/**
* @author lvyq
* @version 1.0
* @description: TODO
* @date 2021/9/24 9:14
*/
@Data
@Document("sys_user")
public class SysUser extends BasePageData {
@Id
private String id;
/**
* 用户id
*/
private String userId;

/**
* 昵称
*/
private String nickName;


/**
* 头像地址
*/

private String heardUrl;

/**
* 性别
*/
private Integer sex;

/**
* 登录状态 0未登录 1已登录
*/
private Integer loginState;

/**
* 用户发给自己且未读的消息数
*/
@Transient
private long unReadMsgNum;

/**
* 最新一条消息
*/
@Transient
private String lastMsg;

/**
* 最新一条消息时间
*/
@Transient
private String lastMsgTime;

}

2.移动端

1.前端代码:

说明:userid在登录系统时存储在了cookie中

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=0">
<title>问题解答</title>
<script src="../../common/js/GBCookie.js"></script>

<style type="text/css">
body {
overflow: hidden;
}

h3 {
text-align: center;
height: 40px;
line-height: 40px;
border-bottom: 0.5px solid #ccc;
font-size: 16px;
font-weight: 300;
}

.chat {
background: #f3f3f3;
/*width: 700px;*/
/*height: 600px;*/
width: 100vw;
height: 100vh;
}

.chat > h3 {
height: 5vh;
line-height: 5vh;
}

.content {
overflow: auto;
/*height: 500px;*/
height: 75vh;
}

ul {
list-style: none;
}

ul > li {
width: 100px;
text-align: center;
line-height: 50px;
width: 100%;
margin-top: 3px;
}

img {
height: 45px;
vertical-align: bottom;
border-radius: 50%;
}

.input {
height: 180px;
border-top: 0.5px solid #f3f3f3;
}

textarea {
width: 100%;
border: 0px;
resize: none;
height: 20vh;
}

span {
display: inline-block;
padding: 0px 5px;
height: 37px;
/*line-height: 37px;*/
line-height: 30px;

}

span:hover .times {
opacity: 1;
transform: translate(0px);
}

span:hover .time {
opacity: 1;
transform: translate(0px);
}

.times {
-webkit-transition: 0.5s;
transition: 0.5s;
opacity: 0;
-webkit-transform: translate(20px);
transform: translate(20px);
}

.time {
-webkit-transition: 0.5s;
transition: 0.5s;
opacity: 0;
-webkit-transform: translate(-20px);
transform: translate(-20px);
}

.he {
background: #e9e9e9;
border-radius: 5px;
border-bottom-right-radius: 15px;

}

.i {
color: #f3f3f3;
background: #5d9aff;
border-radius: 5px;
border-bottom-left-radius: 14px;
}

.state {
font-size: 12px;
}

.readOrUnread {
/*position: absolute;*/
/*right: 55px;*/
/*bottom: -39px;*/
/*font-size: 12px;*/
margin-right: 55px;
}

.content li > span {
position: relative;
}

.content li {
margin-top: 15px;
}

li .i,
li .he {
max-width: 50vw;
word-wrap: break-word;
word-break: break-all;
text-align: left;
height: auto;
padding: 7px 5px;
}
</style>
<script src="../../common/vue/vue.min.js"></script>
<script src="../../common/axios/axios.min.js"></script>
<link rel="stylesheet" href="../../common/css/base.css">
</head>
<body>

<div id="app">
<div class="chat"><h3>客服<span class="state"></span></h3>
<div class="content" ref="reference">
<ul style="color: black">
<li v-for="item in content"
:key="item.id"
:style="item.flag ? 'text-align: right;' : 'text-align: left;'">
<span>
<img v-show="!item.flag" src="../img/icon/01.png" />
<span class="times" v-show="item.flag">{{
item.createTime
? item.createTime.substr(11, item.createTime.length)
: ""
}}</span>
<span :class="item.flag ? 'i' : 'he'">{{ item.name }}</span>
<span class="time" v-show="!item.flag">{{
item.createTime
? item.createTime.substr(11, item.createTime.length)
: ""}}
</span>
<img
v-show="item.flag"
:src="heardUrl"
/>
<div class="readOrUnread" v-if="item.flag">
{{ item.readState == "1" ? "已读" : "未读" }}
</div>
</span>
</li>
</ul>
</div>
<div class="input">
<textarea
cols="50"
rows="10"
v-model="text"
@keydown.enter="submit"
></textarea>
</div>
</div>
</div>

<script type="text/javascript">
var app = new Vue({
el: "#app",
data() {
return {
text: "",
content: [],
socket: "",
userId: "",
openId: "",
heardUrl: "",
webUrl: ""
}
},
created() {
this.openId = getCookie('openId');
},
mounted() {
this.getWebUrl()
this.getSysUserInfoByUserId()
},
methods: {
submit() {
if (!this.text) {
return;
}
this.content.push({
flag: 1,
name: this.text,
});
this.socket.send(
JSON.stringify({
linkType: "msg",
sendId: this.openId,
userId: this.openId,
receiveId: 1,
msgKey: this.openId + '_1',
chatMsg: this.text,
readState: 0,
})
);
this.text = null;
this.scrollAuto();
},
scrollAuto() {
this.$nextTick(() => {
this.$refs["reference"].scrollTop = this.$refs["reference"].scrollHeight
});
},
history() {
axios({
method: "post",
url: "../../../chat/msgHistory",
data: {
receiveId: 1,
sendId: this.openId,
pageSize: "20",
currentPage: "1",
}
}).then((res) => {
if (res) {
const {data} = res;
this.content = data.data.list.reverse();
this.scrollAuto();
}
})
this.msgRead()
},
msgRead() {
let url = `http://${this.webUrl}/chat/msgRead`
console.log(url, "url")
axios({
method: "post",
url: url,
data: {
sendId: this.openId,
receiveId: `1`,
}
})
},
getWebUrl() {
axios({
method: "POST",
url: "../../../chat/getWebUrl",
}).then((res) => {
if (res) {
const {data} = res
this.webUrl = data.webUrl
this.history()
this.initWs()
}

})
},
initWs() {
if (typeof (WebSocket) == "undefined") {
console.log("遗憾:您的浏览器不支持WebSocket");
} else {
console.log("恭喜:您的浏览器支持WebSocket");
this.socket = new WebSocket(`ws://${this.webUrl}/ws/asset`);
//连接打开事件
this.socket.onopen = function () {
console.log("Socket 已打开");
//session与用户id绑定
this.socket.send("{\"linkType\":\"bind\",\"sendId\":\""${this.sendId}"\"} ");
};
//收到消息事件
this.socket.onmessage = (data) => {
console.log(data)
if (data.data !== '连接成功') {
let news = JSON.parse(data.data);
this.content.push({
flag: 0,
name: news.chatMsg,
});
//对方接收到消息之后,把未读改为已读
if (news.code == 0) {
this.content.pop()
for (var i = 0; i < this.content.length; i++) {
this.content[i].readState = 1
}
}
this.msgRead()
this.scrollAuto();
}

};
// socket.
//连接关闭事件
this.socket.onclose = function () {
console.log("Socket已关闭");
};
//发生了错误事件
this.socket.onerror = function () {
alert("Socket发生了错误");
}

//窗口关闭时,关闭连接
window.unload = function () {
this.socket.close();
};
}
setTimeout(() => {
this.socket.send(
JSON.stringify({
linkType: "bind",
sendId: this.openId,
})
);
}, 500);
},
//通过openId获取用户头像信息
getSysUserInfoByUserId() {
axios({
method: "GET",
url: "../../../chat/getSysUserInfoByUserId",
params: {
"openId": this.openId
}
}).then((res) => {
if (res.data.success) {
if (res.data.data) {
this.heardUrl = res.data.data.heardUrl
console.log(this.heardUrl)
}

}
})
}
}
})

</script>
</body>
</html>

GBCookie.js

function setCookie(c_name, value, expiredays) {
var exdate = new Date()
exdate.setDate(exdate.getDate() + expiredays)
document.cookie = c_name + "=" + escape(value) +
((expiredays == null) ? "" : ";expires=" + exdate.toGMTString())
}

//取回cookie
function getCookie(c_name) {
if (document.cookie.length > 0) {
c_start = document.cookie.indexOf(c_name + "=")`在这里插入代码片`
if (c_start != -1) {
c_start = c_start + c_name.length + 1
c_end = document.cookie.indexOf(";", c_start)
if (c_end == -1) c_end = document.cookie.length
return unescape(document.cookie.substring(c_start, c_end))
}
}
return ""
}
2.后端代码

引入mongodb依赖

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
<version>2.1.6.RELEASE</version>
</dependency>

yml配置

#mongodb 配置
spring:
data:
mongodb:
host: 192.168.1.125
port: 27017
database: cangzhou
# 客服消息地址
websocket:
url: 192.168.1.209:9019/fmis

WxChatController

import com.alibaba.fastjson.JSONObject;
import com.jinmdz.common.response.CommonCode;
import com.jinmdz.entity.wechat.SysUser;
import com.jinmdz.entity.wechat.WeChatResult;
import com.jinmdz.model.chat.ChatMsg;
import com.jinmdz.service.ChatService;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;

/**
* @author lvyq
* @version 1.0
* @description: TODO
* @date 2021/11/12 13:18
*/
@RestController
@RequestMapping("/chat")
public class WxChatController {
@Resource
public MongoTemplate mongoTemplate;

@Resource
private ChatService chatService;

/**
* @description: 添加用户-测试
* @author: lvyq
* @date: 2021/11/18 16:08
* @version 1.0
*/

@PostMapping("/saveUser")
public WeChatResult saveUser(@RequestBody SysUser user){
Query query = new Query().addCriteria(Criteria.where("userId").is(user.getUserId()));
Update update = new Update().set("nickName",user.getNickName()).set("heardUrl",user.getHeardUrl());
SysUser sysUser= mongoTemplate.findOne(query,SysUser.class);
if (sysUser!=null){
mongoTemplate.updateFirst(query,update,SysUser.class);
}else {
mongoTemplate.insert(user);

}
return new WeChatResult(CommonCode.SUCCESS);
}
/**
* @description: 消息记录
* @author: lvyq
* @date: 2021/11/12 13:18
* @version 1.0
*/
@PostMapping("/msgHistory")
public JSONObject msgHistory(@RequestBody ChatMsg data){
return chatService.msgHistory(data);
}



/**
* @description: 获取ws的地址
* @author: lvyq
* @date: 2021/11/16 14:45
* @version 1.0
*/

@PostMapping("/getWebUrl")
public JSONObject getWebUrl(){
return chatService.getWebUrl();
}

/**
* @description: 根据用户openId获取用户基本信息
* @author: lvyq
* @date: 2021/11/18 16:27
* @version 1.0
*/

@GetMapping("/getSysUserInfoByUserId")
public WeChatResult getSysUserInfoByUserId(String openId){
if (StringUtils.isEmpty(openId)){
return new WeChatResult(CommonCode.INVALID_PARAM);
}
Query query = new Query().addCriteria(Criteria.where("userId").is(openId));
SysUser sysUser = mongoTemplate.findOne(query,SysUser.class);
return new WeChatResult(sysUser);
}

}

ChatService

import com.alibaba.fastjson.JSONObject;
import com.jinmdz.model.chat.ChatMsg;

/**
* @author lvyq
* @version 1.0
* @description: TODO
* @date 2021/11/12 13:18
*/
public interface ChatService {
JSONObject msgHistory(ChatMsg data);

JSONObject getUnReadNum(ChatMsg data);

JSONObject getWebUrl();
}

ChatServiceImpl

import com.alibaba.fastjson.JSONObject;
import com.jinmdz.model.chat.ChatMsg;
import com.jinmdz.service.ChatService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* @author lvyq
* @version 1.0
* @description: TODO
* @date 2021/11/12 13:19
*/
@Service
public class ChatServiceImpl implements ChatService {

@Resource
private MongoTemplate mongoTemplate;

@Value("${websocket.url}")
public String WebUrl;

/**
* @description: 消息记录
* @author: lvyq
* @date: 2021/9/26 15:20
* @version 1.0
*/

@Override
public JSONObject msgHistory(ChatMsg data) {
JSONObject mav = new JSONObject();
int page = data.getCurrentPage();
if (page<0){
page=1;
}
page=page-1;
int size = data.getPageSize();
if (size<0){
size=20;
}
if (!StringUtils.isEmpty(data.getSendId()) && !StringUtils.isEmpty(data.getReceiveId())){
String msgKeyOne= data.getSendId()+"_"+data.getReceiveId();
String msgKeyTwo = data.getReceiveId()+"_"+data.getSendId();
Criteria orCri1 = new Criteria();
Criteria orCri2 = new Criteria();
orCri1.andOperator(Criteria.where("msgKey").is(msgKeyOne));
orCri2.andOperator(Criteria.where("msgKey").is(msgKeyTwo));
Query query = new Query().addCriteria(new Criteria().orOperator(orCri1,orCri2));
long total = mongoTemplate.count(query,ChatMsg.class);
Pageable pageable = PageRequest.of(page,size, Sort.by(Sort.Direction.DESC,"createTime"));
query.with(pageable);
List<ChatMsg> list = mongoTemplate.find(query,ChatMsg.class);
HashMap<String, Object> map = new HashMap<>();
if (list != null) {
for (ChatMsg chatMsg:list){
chatMsg.setName(chatMsg.getChatMsg());
if (chatMsg.getSendId().equals(data.getSendId())){
chatMsg.setFlag(1);
}else {
chatMsg.setFlag(0);
}
}
Map<String, Object> pageMap = new HashMap<>();
map.put("list", list);
pageMap.put("total", total);
pageMap.put("pageSize", data.getPageSize());
pageMap.put("currentPage", data.getCurrentPage());
map.put("pager", pageMap);
}
Query queryMsg = new Query().addCriteria(Criteria.where("readState").is("0").and("sendId").is(data.getReceiveId()).and("receiveId").is(data.getSendId()));
Update update = new Update().set("readState","1");
mongoTemplate.updateFirst(queryMsg,update,ChatMsg.class);
mav.put("state",true);
mav.put("data",map);
return mav;
}else {
mav.put("state",false);
mav.put("msg","非法参数");
return mav;
}

}

/**
* @description: 未读消息条数
* @author: lvyq
* @date: 2021/11/16 14:20
* @version 1.0
*/

@Override
public JSONObject getUnReadNum(ChatMsg data) {
JSONObject mav = new JSONObject();
Query query = new Query();
query.addCriteria(Criteria.where("readState").is("0").and("sendId").is(data.getSendId()).and("receiveId").is(data.getReceiveId()));
long count = mongoTemplate.count(query,ChatMsg.class);
mav.put("state",true);
mav.put("unReadNum",count);
return mav;
}




/**
* @description: 获取websocket地址
* @author: lvyq
* @date: 2021/11/16 14:47
* @version 1.0
*/
@Override
public JSONObject getWebUrl() {
JSONObject mav = new JSONObject();
mav.put("state",true);
mav.put("webUrl",WebUrl);
return mav;
}

}