当前位置:   article > 正文

SpringBoot和Vue2集成WebSocket,实现聊天室功能_vue+java基于websocket实现多用户在线客服聊天系统

vue+java基于websocket实现多用户在线客服聊天系统

SpringBoot和Vue2集成WebSocket,实现聊天室功能

1.加入依赖
2.后端建立socket服务端
3.前端建立客户端
  • 1
  • 2
  • 3

后端

<!-- websocket -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>
		<dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.76</version>
        </dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
// 配置开启WebSocket
@Configuration
public class WebSocketConfig {

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

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
/**
 * @author websocket服务
 */
@ServerEndpoint(value = "/imserver/{userId}")
@Component
public class WebSocketServer {


    private static UserService userService;

    private static RedisTemplate redisTemplate;

    public static void setUserService(ApplicationContext context){
        userService = context.getBean(UserServiceImpl.class);
        redisTemplate = (RedisTemplate) context.getBean("redisTemplate");
    }


    private static final Logger log = LoggerFactory.getLogger(WebSocketServer.class);
    /**
     * 记录当前在线连接数
     */
    public static final Map<String, Session> sessionMap = new ConcurrentHashMap<>();
    /**
     * 连接建立成功调用的方法
     */
    // 当前用户
    private UserVo userVo;
    // 连接上服务端触发的方法
    @OnOpen
    public void onOpen(Session session, @PathParam("userId") String userId) {
        if (StringUtils.hasText(userId)){
            // 加入新用户
            if (sessionMap.containsKey(userId)){
                sessionMap.remove(userId);
            }
            sessionMap.put(userId, session);
            this.userVo = userService.findById(Long.valueOf(userId));
            // 统计所有在线用户
            List<UserVo> list = new LinkedList<>();
            sessionMap.forEach((userId1,session1) -> {
                UserVo userVo = userService.findById(Long.valueOf(userId1));
                list.add(userVo);
            });
            try {
                // 发送给所有在线的用户,更新在线人数
                sendAllMessage(JSON.toJSONString(list));
            } catch (Exception e) {
                e.printStackTrace();
            }
            log.info("有新用户加入,userId={}, 当前在线人数为:{}", userId, sessionMap.size());
        }

    }
    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose(Session session, @PathParam("userId") String userId) {
        sessionMap.remove(userId);
        // 统计所有在线用户
        List<UserVo> list = new LinkedList<>();
        sessionMap.forEach((userId1,session1) -> {
            UserVo userVo = userService.findById(Long.valueOf(userId1));
            list.add(userVo);
        });
        sendAllMessage(JSON.toJSONString(list));
        log.info("有一连接关闭,移除userId={}的用户session, 当前在线人数为:{}", userId, sessionMap.size());
    }
    /**
     * 收到客户端消息后调用的方法
     * 后台收到客户端发送过来的消息
     * onMessage 是一个消息的中转站
     * 接受 浏览器端 socket.send 发送过来的 json数据
     * @param message 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message, Session session, @PathParam("userId") String userId) {
        userVo = userService.findById(Long.valueOf(userId));
        log.info("服务端收到用户username={},id={}的消息:{}", userVo.getNickname(),userId, message);
        // 解析消息
        JSONObject jsonObject1 = JSON.parseObject(message);
        String toUserId = jsonObject1.getString("toUserId");
        String text = jsonObject1.getString("text");
        // 判断是给指定人发,还是群发
        if (StringUtils.hasText(toUserId)){
            // {"to": "admin", "text": "聊天文本"}
            Session toSession = sessionMap.get(toUserId); // 根据 to用户名来获取 session,再通过session发送消息文本
            if (toSession != null) {
                // 服务器端 再把消息组装一下,组装后的消息包含发送人和发送的文本内容
                // {"from": "zhang", "text": "hello"}
                JSONObject jsonObject = new JSONObject();
                jsonObject.put("fromUser",userVo);
                jsonObject.put("toUser",userService.findById(Long.valueOf(toUserId)));
                jsonObject.put("text",text);
                this.sendMessage(jsonObject.toJSONString(), toSession);
                log.info("发送给用户userId={},消息:{}", toUserId, jsonObject.toJSONString());
            } else {
                log.info("发送失败,未找到用户username={}的session", toUserId);
            }
        }else{
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("fromUser",userVo);
            jsonObject.put("text",text);
            this.sendAllMessage(jsonObject.toJSONString());
            // 将消息存入redis
            redisTemplate.opsForList().rightPush("messageList",jsonObject.toJSONString());
            redisTemplate.expire("messageList",60*60, TimeUnit.SECONDS); // 过期时间

            log.info("发送给所有用户,消息:{}", toUserId, jsonObject.toJSONString());

        }
    }
    @OnError
    public void onError(Session session, Throwable error) {
        log.error("发生错误");
        error.printStackTrace();
    }
    /**
     * 服务端发送消息给客户端
     */
    private void sendMessage(String message, Session toSession) {
        try {
            log.info("服务端给客户端[{}]发送消息{}", toSession.getId(), message);
            toSession.getBasicRemote().sendText(message);
        } catch (Exception e) {
            log.error("服务端发送消息给客户端失败", e);
        }
    }
    /**
     * 服务端发送消息给所有客户端
     */
    private void sendAllMessage(String message) {
        try {
            for (Session session : sessionMap.values()) {
                log.info("服务端给客户端[{}]发送消息{}", session.getId(), message);
                session.getBasicRemote().sendText(message);

            }
        } catch (Exception e) {
            log.error("服务端发送消息给客户端失败", e);
        }
    }
}

// WebSocket服务类无法进行bean的注入,所以要自己调用ApplicationContext获取bean再注入

@SpringBootApplication
public class BlogApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext applicationContext = SpringApplication.run(BlogApplication.class, args);
        WebSocketServer.setUserService(applicationContext);
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156

前端

<template>
  <div class="socket">
    <el-row>
      <el-col :span="6" class="online">
          <h3 style="background:linear-gradient(to left,#cae9ff,#c7d8ff);text-align:center;padding:5px;">在线用户</h3>
          <el-scrollbar style="height:280px">
          <div v-for="user in onlineUser" :key="user.id" style="padding:5px" class="onlineUser">
            <el-avatar shape="square" size="medium" :src="user.avatar"></el-avatar>
            <span style="margin-left:8px">{{user.account}}</span>
          </div>
          </el-scrollbar>
      </el-col>
      <el-col :span="17" class="container">
          <h3 style="background:linear-gradient(to left,#cae9ff,#c7d8ff);text-align:center;padding:5px;">当前用户(<span style="color:#66b1ff">{{user.account}}</span>)</h3>
          <el-scrollbar style="height:420px" ref="scroll">
          <div class="content">
            <div class="message" v-for="item,index in messageList" :key="index" style="margin-top:5px;margin-left:5px;position:relative" :style="{justifyContent: item.fromUser.id == user.id ? 'flex-end' : 'flex-start'}">
              <el-avatar shape="square" size="medium" :src="item.fromUser.avatar" :style="{order:user.id == item.fromUser.id ? '1':'0'}"></el-avatar>
              <span style="margin-left:8px;" class="text">{{item.text}}</span>
            </div>
          </div>
          </el-scrollbar>
          <div style="margin-top: 15px;" class="write">
            <el-input placeholder="请输入内容" maxlength="100" clearable v-model="text" class="input-with-select">
              <el-button slot="append" type="primary" @click="sendMessage">发送</el-button>
            </el-input>
          </div>
      </el-col>
    </el-row>
  </div>
</template>

<script>
  import config from '@/utils/config'
  import {getCacheMessage} from '@/api/socket'
  import {mapState} from 'vuex'
  export default {
    name:'lm',
    data(){
      return {
        text:'',
        onlineUser:[],
        messageList:[],
        socket:'',
        baseUrl:config.socketUrl     
      }
    },
    computed:{
      ...mapState(['user'])
    },
    methods:{
      // 连接socket
      onOpen(){
        if (typeof WebSocket == 'undefined'){
          console.log('你的浏览器不支持webSocket')
          return;
        }
        const url = this.baseUrl+this.user.id
        
        this.socket = new WebSocket(url);

        this.socket.onopen = ()=>{
          console.log('websocket打开了,'+this.user.id+'连接成功')
        }
        this.socket.onmessage = (data)=>{
          var message = JSON.parse(data.data)
          if(message.hasOwnProperty("text")){
            this.messageList.push(message)
            this.text = ''
            let scroll = this.$refs['scroll'].$refs['wrap']
            this.$nextTick(() =>{
              scroll.scrollTop = scroll.scrollHeight
            })
          }else{
            // 统计在线人数
            this.onlineUser = message
            
          }
        }
        this.socket.onclose = function(){
          console.log('断开连接成功')
        }
      },
      sendMessage(){
        const message = {
          text:this.text,
          userId:this.user.id
        }
        if(this.text == ''||this.text == null){
          this.$message.warning('请输入内容')
        }else{
          this.socket.send(JSON.stringify(message))
        }
      },
      // 初始化,缓存的消息
      getCacheMessage(){
        getCacheMessage().then(
          res => {
            const data = res.data
            for(var i in data){
              this.messageList.push(JSON.parse(data[i]))
            }
          }
        )
      }
    },
    mounted(){
      this.getCacheMessage()
      this.onOpen()
    },
    beforeDestroy(){
      this.socket.close() // 关闭socket
    }

  }
</script>

<style scoped>
  .socket{
    width: 1200px;
    height: 600px;
    padding:26px;
    background-color: #fff;
    border-radius: 5px;
  }
  .socket .online{
    background-color: #f5f5f5;
    height: 300px;
    border-radius: 5px;
    overflow: hidden;
  }
  .socket .online .onlineUser{
    display: flex;
    justify-content: flex-start;
    align-items: flex-start;  
  }
  .socket .container{
    position: relative;
    margin-left: 26px;
    background-color: #f5f5f5;
    height: 500px;
    border-radius: 5px;
  }
  .socket .el-button--primary{
    background-color: #66b1ff;
    color: #fff;
  }
  .socket .container .content{
    min-height: 400px;
    border: 1px solid #d6d6d6;
    border-radius: 4px;
  }
  .socket .container .content .message{
    display: flex;
    align-content: flex-start;
  }
  .socket .write{
    position:absolute;
    bottom: 10px;
    width: 500px;
    right: 50%;
    transform: translateX(50%);
  }

  .socket .text{
    min-height: 24px;
    line-height: 20px;
    padding: 8px;
    font-size: 16px;
    background-color: #fff;
    border-radius: 5px;
    max-width: 280px;
    overflow-wrap:break-word;
    word-wrap:break-word;
    word-break: bread-all;
    white-space:pre-wrap;
    overflow: hidden;
  }
</style>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179

springboot集成websocket实现聊天室的功能。如有不足之处,还望大家斧正。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/运维做开发/article/detail/892361
推荐阅读
相关标签
  

闽ICP备14008679号