websocket实现在线客服系统

websocket实现在线客服系统

1、后端

先实现一个端点服务。注意:在websocket中导入ChatService时候必须是static的,不然会是null。

@Slf4j
@Component
@ServerEndpoint("/stu/chat/{name}")
public class ChatController {

    private static ChatService chatMsgService;

    @Autowired
    public void setChatService(ChatService chatService) {
        ChatController.chatMsgService = chatService;
    }

    private static final Map<String, Session> clients = new ConcurrentHashMap<>();

    @OnOpen
    public void onOpen(@PathParam("name") String name, Session session) {
        log.info("有新的连接:{}", name);
        add(name, session);
        log.info("当前在线用户数:{}", clients.size());
    }

    /**
     * 发送消息,前端将消息转成json字符串,后端转成对象
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        ObjectMapper mapper = new ObjectMapper();
        try {
            ChatMsg msg = mapper.readValue(message, ChatMsg.class);
            if ("1".equals(msg.getSendType())) {
                // 广播
                sendMessageAll(msg.getMsg(), msg.getSendUser());
            } else if ("2".equals(msg.getSendType())) {
                // 群聊
                sendMessageGroup(msg.getMsg(), msg.getMsg());
            } else if ("3".equals(msg.getSendType())) {
                Session se = clients.get(msg.getAcceptUser());
                if (se != null) {
                    sendMessage(se, msg);
                } else {
                    msg.setIsRead("0");
                }
                new Thread(() -> {
                    if (chatMsgService == null) {
                        log.error("保存聊天消息出错:chatMsgService为null");
                        return;
                    }
                    chatMsgService.addMsg(msg);
                }).start();
            }
        } catch (JsonProcessingException e) {
            log.info("发送消息出错");
            e.printStackTrace();
        }
    }

    @OnClose
    public void onClose(@PathParam("name") String name, Session session) {
        log.info("{}:退出聊天", name);
        remove(name);
        log.info("当前在线用户数:{}", clients.size());
    }

    @OnError
    public void one rror(Session session, Throwable throwable) {
        try {
            session.close();
        } catch (IOException e) {
            log.error("退出发生异常: {}", e.getMessage());
        }
        log.info("连接出现异常: {}", throwable.getMessage());
    }

    /**
     * 新增一个连接
     */
    public static void add(String name, Session session) {
        if (!name.isEmpty() && session != null) {
            clients.put(name, session);
        }
    }

    /**
     * 删除一个连接
     */
    public static void remove(String name) {
        if (!name.isEmpty()) {
            clients.remove(name);
        }
    }

    /**
     * 获取在线人数
     */
    public static int count() {
        return clients.size();
    }

    /**
     * 广播
     *
     * @param message  发送的消息
     * @param username 发送人
     */
    public static void sendMessageAll(String message, String username) {
        log.info("广播消息:{}", message);
        clients.forEach((key, session) -> {
            if (!username.equals(key)) {
                RemoteEndpoint.Async remote = session.getAsyncRemote();
                if (remote == null) {
                    return;
                }
                remote.sendText(message);
            }
        });
    }

    /**
     * 群聊
     *
     * @param message  发送的消息
     * @param username 发送人
     */
    public static void sendMessageGroup(String message, String username) {
        log.info("群发消息");
        clients.forEach((key, session) -> {
            if (!username.equals(key)) {
                RemoteEndpoint.Async remote = session.getAsyncRemote();
                if (remote == null) {
                    return;
                }
                remote.sendText(message);
            }
        });
    }

    /**
     * 单聊
     *
     * @param session session
     * @param msg     发送的消息
     */
    public static void sendMessage(Session session, ChatMsg msg) {
        if (session == null) {
            return;
        }
        final RemoteEndpoint.Async basic = session.getAsyncRemote();
        if (basic == null) {
            return;
        }
        String s = JSONArray.toJSON(msg).toString();
        basic.sendText(s);
    }
}

编写配置文件,如果在系统有spring的定时任务时候,websocket会和spring的定时任务冲突,导致报错,解决办法如下代码。

@Configuration
@EnableWebSocket
public class SocketConfig {

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

    // 解决不能同时使用websocket和spring的定时注解
    @Bean
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler scheduling = new ThreadPoolTaskScheduler();
        scheduling.setPoolSize(10);
        scheduling.initialize();
        return scheduling;
    }
}

2、前端使用vue

<template>
  <section>
    <div class="chat-section">
      <div class="leftDiv">
        <li class="infinite-list-head">
          <img class="head-img-size cut-circle" :src="'/api'+userInfo.imgPath"/> {{ userInfo.name }}
        </li>
        <div class="friends-list">
          <ul class="infinite-list">
            <li v-for="item in friends" :key="item.id" @click="chat(item)" class="infinite-list-item">
              <img class="head-img-size cut-circle" :src="'/api'+item.imgPath"/> {{ item.name }}
            </li>
          </ul>
        </div>
      </div>
      <div class="chat-div">
        <div class="chat-title"><span><img class="head-img-size cut-circle" :src="'/api'+current.imgPath" />{{ current.name }}</span>		  </div>
        <hr/>
        <div class="news">
          <template v-for="(item,index) in sendMessage">
            <!--时间/通知等-->
            <div v-if="item.msgType === '10'" class="chat-notice">
              <span>{{ timeFormat(item.sendTime) }}</span>
            </div>

            <!-- 发送的消息 -->
            <div v-if="item.sendUser === userInfo.id" class="chat-sender">
              <div><img class="cut-circle" :src="'/api'+userInfo.imgPath"/></div>
              <div>{{ userInfo.name }}</div>
              <div>
                <div class="chat-right_triangle"></div>
                <span> {{ item.msg }} </span>
              </div>
            </div>

            <!-- 接收的消息 -->
            <div v-if="item.acceptUser === userInfo.id && item.sendUser === current.id" class="chat-receiver">
              <div><img class="cut-circle" :src="'/api'+current.imgPath"/></div>
              <div>{{ current.name }}</div>
              <div>
                <div class="chat-left_triangle"></div>
                <span> {{ item.msg }} </span>
              </div>
            </div>
          </template>
        </div>
        <hr/>
        <div>
          <div class="expression">
            <!--表情-->
            <el-popover placement="top" width="320" trigger="click">
            <span v-for="index in 70" :key="index">
              <img class="expression-emjio" @click="selectEmjio(index)"
                   :src="require('@/assets/img/emjio/'+index +'.png')"/>
            </span>
              <i slot="reference" class="iconfont icon-smiling"></i>
            </el-popover>
            <i class="iconfont icon-wenjian"></i>
          </div>
          <div class="send-message">
            <el-input
              type="textarea"
              :rows="2"
              placeholder="请输入内容"
              class="send-textarea"
              v-model="content">
            </el-input>
            <span><el-button type="primary" class="send-but" @click="sendBut">发 送</el-button></span>
          </div>
        </div>
      </div>
    </div>
  </section>
</template>

<script>
  export default {
    name: "chat",
    data() {
      return {
        friends: [],
        userInfo: '',   // 当前用户信息
        current: '',    // 当前聊天对象
        content: '',    // 内容
        em: ["惊讶", "难过", "亲亲", "再见", "奋斗", "鄙视", "得意", "坏笑", "调皮", "笑哭", "口吐芬芳", "微笑", "皱眉", "尴尬", "厉害", "吃瓜", "凶狠", "绿帽", "鼓掌", "非洲酋长", "好色", "微笑温暖", "偷笑", "无奈", "疑问", "犯困", "发呆", "有趣", "大哭", "白眼", "衰", "生病", "听歌", "打脸", "摸头", "吐血", "财", "机智", "敲头", "666", "海绵宝宝1", "闭嘴", "可怜", "拒绝", "发火", "害羞1", "无奈流汗", "害羞2", "感动", "胜利", "抓狂", "伤心", "海绵宝宝2", "送爱心", "笑哭2", "晕", "骷髅", "地雷", "击掌", "赞", "握手", "低头", "可爱皱眉", "气爆炸", "呕吐", "惊吓", "吓坏", "亲", "心", "玫瑰"],
        sendMessage: [],  // 存储消息数组
        websocket: '',
      }
    },

    created() {
      this.getFriends();
      this.initWebSocket();
    },

    destroyed() {
      this.websocket.close();
    },

    methods: {
      // 切换对象
      chat(va) {
        this.sendMessage = [];
        this.content = '';
        this.current = va;
      },

      // 连接websocket
      initWebSocket() {
        const wsuri = 'ws://localhost:2020/stu/chat/' + this.userInfo.id;
        this.websocket = new WebSocket(wsuri);
        this.websocket.onmessage = this.websocketonmessage;
      },

      // 接收数据
      websocketonmessage(e) {
        let parse = JSON.parse(e.data);

        this.sendMessage.push(parse);
      },

      // 数据发送
      websocketsend(agentData) {
        this.websocket.send(agentData);
      },

      // 获取好友列表
      async getFriends() {
        this.userInfo = JSON.parse(localStorage.getItem("userInfo"));
        await this.api.getApi("/chat/friends/get?id=" + this.userInfo.id).then(res => {
          this.friends = res.data;
          if (this.friends.length > 0) {
            this.current = this.friends[0];
          }
        })
      },

      // 发送按钮
      sendBut() {
        if (this.content !== '') {
          // 第一条消息之前加上时间
          if (this.sendMessage.length === 0) {
            this.timeAdd();
          }
          let msg = {
            sendUser: this.userInfo.id,
            acceptUser: this.current.id,
            msg: this.content,
            msgType: '',
            sendType: '',
            isRead: '1',
            sendTime: new Date()
          }
          // 判断消息类型,发送类型
          msg.msgType = '1';
          msg.sendType = '3';
          this.sendMessage.push(msg);
          let s = JSON.stringify(msg);
          this.websocketsend(s);
          this.content = '';
        }
      },

      // 选择表情
      selectEmjio(value) {
        let split = this.em[value - 1];
        let s = this.content + '[' + split + ']';
        this.content = s;
      },

      // 时间添加
      timeAdd() {
        let msg = {
          sendUser: '',
          acceptUser: '',
          msg: '',
          msgType: '10',
          sendType: '',
          isRead: '',
          sendTime: new Date()
        }
        this.sendMessage.push(msg);
      },

      // 时间判断,上一条消息和当前消息差距超过1小时时候显示一条
      timeJudgment() {

      },

      // 时间格式化
      timeFormat(te) {
        let timedate, s, mm, h, d, m, y, time;
        if (te == '') {
          return '';
        } else if (te.length == 10) {
          time = new Date(te * 1000);
          y = time.getFullYear();
          m = time.getMonth() < 9 ? '0' + (time.getMonth() + 1) : time.getMonth() + 1;
          d = time.getDate() < 10 ? '0' + time.getDate() : time.getDate();
          h = time.getHours() < 10 ? '0' + time.getHours() : time.getHours();
          mm = time.getMinutes() < 10 ? '0' + time.getMinutes() : time.getMinutes();
          s = time.getSeconds() < 10 ? '0' + time.getSeconds() : time.getSeconds();
          timedate = y + '年' + m + '月' + d + '日 ' + h + ':' + mm + ':' + s;
          return timedate;
        } else {
          time = new Date(te);
          y = time.getFullYear();
          m = time.getMonth() < 9 ? '0' + (time.getMonth() + 1) : time.getMonth() + 1;
          d = time.getDate() < 10 ? '0' + time.getDate() : time.getDate();
          h = time.getHours() < 10 ? '0' + time.getHours() : time.getHours();
          mm = time.getMinutes() < 10 ? '0' + time.getMinutes() : time.getMinutes();
          s = time.getSeconds() < 10 ? '0' + time.getSeconds() : time.getSeconds();
          timedate = y + '年' + m + '月' + d + '日 ' + h + ':' + mm + ':' + s;
          return timedate;
        }
      },

      // 查询当前消息
      findMessage() {

      }

    }
  }
</script>

<style scoped>
  .chat-section {
    width: 81%;
    height: 100%;
    margin: 0 auto;
  }

  /* 左边的列表 */
  .leftDiv {
    width: 20%;
    height: 100.2%;
    float: left;
    background-color: #2e2e2e;
  }

  .friends-list {
    overflow-y: auto;
    overflow-x: hidden;
    height: 87%;
  }

  .friends-list::-webkit-scrollbar {
    display: none;
  }

  .infinite-list {
    padding: 0;
    margin: 0;
    color: white;
    list-style-type: none;
  }

  .infinite-list-head {
    color: white;
    height: 50px;
    margin-top: 1px;
    padding: 10px;
    display: flex;
    justify-content: center;
    align-items: center;
    background-color: #2e2e2e;
  }

  .head-img-size {
    width: 30px;
    height: 30px;
    margin-right: 5px;
  }

  /*头像切园*/
  .cut-circle {
    border-radius: 50%;
    overflow: hidden;
  }

  .infinite-list-item {
    cursor: pointer; /*鼠标放上变手*/
    height: 50px;
    margin-top: 1px;
    padding: 10px;
    display: flex;
    /*justify-content: center; !*水平居中*!*/
    align-items: center; /*垂直居中*/
    background-color: #535353;
  }

  /* 右边内容 */
  .chat-div {
    width: 79.7%;
    height: 100%;
    float: left;
    border: solid 1px #a3a3a3;
  }

  .chat-title {
    height: 45px;
    padding-left: 15px;
    display: flex;
    align-items: center;
  }

  .chat-title span {
    font-size: 20px;
  }

  /* 右边中间消息展示 */
  .news {
    width: 99.7%;
    height: 63%;
    overflow-y: auto;
    overflow-x: hidden;
  }

  /*隐藏中间的进度条*/
  .news::-webkit-scrollbar {
    display: none;
  }

  .expression {
    width: 100%;
    height: 30px;
    padding-left: 15px;
  }

  .expression-emjio {
    width: 35px;
    height: 35px;
    display: inline-block;
  }

  .expression i {
    font-size: 20px;
    color: #7d7d7d;
    margin: 0 8px 0 0;
  }

  .send-message {

  }

  .send-textarea {
    margin-left: 5px;
    width: 98%;
  }

  .send-textarea >>> .el-textarea__inner {
    border: 0;
    resize: none;
  }

  .send-message span {
    font-size: 13px;
    margin: 8px 9px 1px 0;
    float: right;
    display: block;
    color: #aaa9a9;
  }

  .send-but {
    margin: 0;
    padding: 0;
    height: 25px;
    width: 50px;
  }

  /*接收的消息*/
  .chat-receiver {
    clear: both;
    font-size: 80%;
  }

  /*聊天气泡效果*/
  .chat-receiver div:nth-of-type(1) {
    float: left;
  }

  .chat-receiver div:nth-of-type(2) {
    margin: 0 50px 2px 50px;
    padding: 0px;
    color: #848484;
    font-size: 70%;
    text-align: left;
  }

  .chat-receiver div:nth-of-type(3) {
    background-color: #27aa95;
    margin: 0px 51% 10px 55px;
    padding: 10px 10px 10px 10px;
    border-radius: 7px;
    text-indent: -12px;
    width: 41%;
  }

  /*发送消息*/
  .chat-sender {
    clear: both;
    font-size: 80%;
  }

  .chat-sender div:nth-of-type(1) {
    float: right;
  }

  .chat-sender div:nth-of-type(2) {
    margin: 0px 50px 2px 50px;
    padding: 0px;
    color: #848484;
    font-size: 70%;
    text-align: right;
  }

  .chat-sender div:nth-of-type(3) {
    background-color: #b2e281;
    margin: 0px 50px 10px 50%;
    padding: 10px 10px 10px 10px;
    border-radius: 7px;
    width: 41%;
  }

  .chat-receiver div:first-child img,
  .chat-sender div:first-child img {
    width: 40px;
    height: 40px;
    /*border-radius: 10%;*/
  }

  .chat-left_triangle {
    height: 0px;
    width: 0px;
    border-width: 6px;
    border-style: solid;
    border-color: transparent #27aa95 transparent transparent;
    position: relative;
    left: -22px;
    top: 3px;
  }

  .chat-right_triangle {
    height: 0px;
    width: 0px;
    border-width: 6px;
    border-style: solid;
    border-color: transparent transparent transparent #b2e281;
    position: relative;
    right: -22px;
    top: 3px;
  }

  .chat-notice {
    clear: both;
    font-size: 70%;
    color: white;
    text-align: center;
    margin-top: 15px;
    margin-bottom: 15px;
  }

  .chat-notice span {
    background-color: #cecece;
    line-height: 25px;
    border-radius: 5px;
    padding: 5px 10px;
  }

  .emjio-img {
    width: 30px;
    height: 30px;
    margin-bottom: -8px;
  }

</style>

3、结果

websocket实现在线客服系统

websocket实现在线客服系统

上一篇:Python忽略错误


下一篇:potato苹果下载 potato苹果版怎么下载