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

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

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全家桶1年46,售后保障稳定

需求场景模拟

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

开发需求

一、技术选型

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

二、需求分析

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

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

效果演示

消息聊天演示:
请添加图片描述
消息时间演示:
在这里插入图片描述
消息未读演示:
在这里插入图片描述

软件需求实现

1.技术架构

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

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

在这里插入图片描述

一、数据库设计

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>

Jetbrains全家桶1年46,售后保障稳定

配置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.后端代码

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;
    }

}

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/179383.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • linux关闭防火墙命令iptables_centos开启防火墙命令

    linux关闭防火墙命令iptables_centos开启防火墙命令1.iptables常用命令1.1查看是否已经安装了iptables以及iptables版本号iptables-V(注意:V是大写字母V)1.2关闭iptablesserviceiptablesstop1.3启动iptablesserviceiptablesstart1.4重启iptablesserviceiptablesrestart1.5保存命令行中设置的ipt…

    2022年9月16日
    0
  • win10笔记本:掉帧卡顿、开机后卡顿、玩游戏帧数低、GPU占用率上不去,解决办法

    win10笔记本:掉帧卡顿、开机后卡顿、玩游戏帧数低、GPU占用率上不去,解决办法win10笔记本:掉帧卡顿、开机后卡顿、玩游戏帧数低、GPU占用率上不去,解决办法

    2022年6月15日
    72
  • C#的Button.DialogResult属性[通俗易懂]

    C#的Button.DialogResult属性[通俗易懂]如果此属性的DialogResult不是设置为None,并且父窗体是通过ShowDialog方法显示的,则不必挂钩任何事件,单击按钮也可关闭父窗体。然后,该窗体的DialogResult属性将设置为该按钮被单击时的DialogResult。例如,若要创建一个“是/否/取消”对话框,只需添加三个按钮并将其DialogResult属性分别设置为Yes、No和Cancel即可。…

    2022年6月22日
    21
  • 机器学习是什么?

    机器学习是什么?WhatisMachineLearning?机器学习现在是一大热门,研究的人特多,越来越多的新人涌进来。不少人其实并没有真正想过,这是不是自己喜欢搞的东西,只不过看见别人都在搞,觉着跟大伙儿

    2022年8月1日
    3
  • Python之数学(math)和随机数(random)

    math包包含了最基本的数学运算函数,如果想要更加高级的数学功能,可以使用标准库外的numpy和scipy库,他们不但支持数组和矩阵运算,还有丰富的数学和物理方程可供使用random包可以用来生成

    2021年12月18日
    59
  • 数据分析sql面试必会6题经典_数据分析师SQL面试必备50题[通俗易懂]

    数据分析sql面试必会6题经典_数据分析师SQL面试必备50题[通俗易懂]以下是SQL面试必备的经典的50道题目,每道题都有博主本人的解题思路和对应的SQL语句。每道题的思路与答案均为博主本人主观理解,仅供参考。环境:MySQL8.0可视化工具:Navicat1、查询课程编号为01的课程比02的课程高的所有学生的学号和成绩解题思路:(1)先把课程为01的学号和成绩找出来as表a(2)再把课程为02的学号和成绩找出来as表b(3)用innerjoin将表a…

    2022年6月28日
    22

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号