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

本文展示了如何在SpringBoot后端和Vue2前端环境中集成WebSocket,实现聊天室功能。主要步骤包括添加依赖、配置WebSocket服务端、创建前端WebSocket客户端,并处理连接打开、关闭、消息接收和发送等事件。同时,代码示例中还包括了用户在线状态管理和消息的存储与分发。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

1.加入依赖
2.后端建立socket服务端
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>
// 配置开启WebSocket
@Configuration
public class WebSocketConfig {

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

}
/**
 * @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);
    }
}

前端

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

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

### 如何在Spring Boot项目中集成语音聊天功能 为了实现Spring Boot项目中的语音聊天功能,可以借鉴已有的技术栈框架来构建这一特性。一种方法是利用WebSocket协议实现即时通讯,并通过第三方服务完成语音处理。 #### 使用WebSocket建立实时通信通道 WebSocket是一种允许服务器主动向客户端推送数据的技术,非常适合用于构建低延迟的双向通信应用,比如在线聊天室。在Spring Boot环境中配置WebSocket相对简单: ```java @Configuration @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void configureMessageBroker(MessageBrokerRegistry config) { config.enableSimpleBroker("/topic"); config.setApplicationDestinationPrefixes("/app"); } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/ws").withSockJS(); } } ``` 这段代码定义了一个基本的消息代理设置以及暴露给前端连接使用的端点[^1]。 #### 集成语音识别与合成API 对于语音输入部分,可以通过调用外部API来进行音频到文本转换;而对于输出,则相反地把文本转化为自然流畅的声音播放出来。这里推荐使用像百度AI开放平台这样的服务商提供的RESTful API接口,它们通常会提供详细的文档说明服务示例帮助开发者快速接入。 假设已经获取到了相应的API密钥其他必要参数,在控制器层面上就可以这样写入逻辑: ```java @RestController @RequestMapping("/api/v1/chat") public class ChatController { private final VoiceService voiceService; @PostMapping("/text-to-speech") ResponseEntity<byte[]> textToSpeech(@RequestBody String message){ try{ byte[] audioBytes = this.voiceService.convertTextToAudio(message); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.parseMediaType("audio/wav")); return new ResponseEntity<>(audioBytes,headers , HttpStatus.OK); }catch (Exception e){ throw new RuntimeException(e.getMessage()); } } // Similar method for speech to text conversion... } ``` 上述例子展示了如何接收来自用户的纯文本请求并返回一段WAV格式的语音文件作为响应[^2]。 #### 客户端交互设计 最后一步是在网页或其他类型的用户界面上创建友好的界面让用户能够轻松发送消息并与之互动。考虑到用户体验,可能还需要考虑一些额外的功能,例如错误提示、加载动画等。 综上所述,通过组合WebSocket技术合适的语音处理工具集,可以在Spring Boot应用程序里成功搭建起一套完整的语音聊天解决方案。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值