欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Docker部署 直播实时弹幕+聊天室 vue+websocket+spring-boot

程序员文章站 2022-05-17 09:29:04
...

Docker部署 实现弹幕+聊天室 vue+websocket+spring-boot

Docker 部署 实现直播后续
参考文章 https://blog.csdn.net/qq_36710522/article/details/90547819
https://blog.csdn.net/qq_36710522/article/details/97103229

做了一些小修改

首先从弹幕功能开始

vue实现弹幕功能很简单,有一个插件叫vue-baberrage,我们只需要安装这个插件就能实现弹幕

cnpm i vue-baberrage

main.js加入

import { vueBaberrage } from 'vue-baberrage'
Vue.use(vueBaberrage);

界面

<vue-baberrage
		:isShow="barrageIsShow"
		:barrageList="barrageList"
		:maxWordCount="maxWordCount"
		:throttleGap="throttleGap"
		:loop="barrageLoop"
		:boxHeight="boxHeight"
		:messageHeight="messageHeight"
		:boxWidth="boxWidth-300"
		>
	</vue-baberrage>
<i-input v-model="sendmsg" placeholder="请输入..." style="width: 300px"></i-input>
<i-button type="primary" @click="addToList(2)">发送弹幕</i-button>
import { MESSAGE_TYPE } from 'vue-baberrage'
			this.barrageList.push({//把数据加进去就可以了
          		id: ++this.currentId,
          		avatar: this.msgHeadImageURL,
          		msg: messageData.chatContent,
          		barrageStyle: "normal",
          		time: 5,
          		type: MESSAGE_TYPE.NORMAL,
          		position: "bottom"
          	});

然后是聊天室

聊天室需要websocket来实现,后端我用的spring-boot 搭建比较快捷

先是前端页面

<template>    
	<div>         
		WebSocket//这是一个样例
	</div>    
</template>
<script>
export default {        
	created() { 
		this.initWebSocket(); //进入页面开始初始化websocket建立连接      
	},        
	destroyed() {
		this.websocketclose(); //离开页面断开连接       
	},        
	methods: {            
		initWebSocket() {                
			// WebSocket与普通的请求所用协议有所不同,ws等同于http,wss等同于https                
			this.websock = new WebSocket("wss://localhost"); //这里是websocket服务地址,这里的地址可以前端根据后台地址参数规则拼成,也可以向后端请求         
			this.websock.onopen = this.websocketonopen;                
			this.websock.onerror = this.websocketonerror;                
			this.websock.onmessage = this.websocketonmessage;                
			this.websock.onclose = this.websocketclose;              
		},              
		websocketonopen() {                
			console.log("WebSocket连接成功");              
		},              
		websocketonerror(e) {                
			console.log("WebSocket连接发生错误");              
		},              
		websocketonmessage(e) {                
			console.log(e.data);                // console.log(e);              
		},              
		websocketclose(e) {                
			console.log("connection closed (" + e.code + ")");              
		},              
	 }    
}
</script>

页面源码

<template>
  <div>

    <div  class="barrages-drop">
      <video
      id="videoId"
      controls = "true"
      :height="boxHeight+350"
      :width="boxWidth">
    </video>
    <vue-baberrage
    :isShow="barrageIsShow"
    :barrageList="barrageList"
    :maxWordCount="maxWordCount"
    :throttleGap="throttleGap"
    :loop="barrageLoop"
    :boxHeight="boxHeight"
    :messageHeight="messageHeight"
    :boxWidth="boxWidth-300"
    >
  </vue-baberrage>
  <Card style="float: right;height: 500px;width: 500px;">
    <Scroll  height="450">
  <ul v-for="item in msgs">
  <li>{{item.chatAvatar}}   {{item.chatTime}}</li>
  <li>  {{item.chatContent}}</li>
</ul>
</Scroll>
  <p  ><i-input v-model="sendmsg" placeholder="请输入..." style="width: 300px"></i-input>
<i-button type="primary" @click="addToList(1)">发送消息</i-button></p>
</Card>
</div>

<i-input v-model="sendmsg" placeholder="请输入..." style="width: 800px"></i-input>
<i-button  type="primary" @click="addToList(2)">发送弹幕</i-button>

<p>当前在线:{{onlineNumber}}</p>
<div v-for="item in onlineUserList">
  <p>{{item}}</p>
</div>
</div>
</template>
<script>
  import { onlineList } from '@/api/data'
  import { MESSAGE_TYPE } from 'vue-baberrage'
  export default {
    name: 'level_2_1',
    data () {
      return {
        type:0,
        sendmsg: '土狗就是个辣鸡~',
        barrageIsShow: true,
        messageHeight: 3,
        boxHeight: 150,
        boxWidth:1000,
        barrageLoop: true,
        maxWordCount: 3,
        throttleGap: 5000,
        barrageList: [],
        t:'',
        userId:0,
        userName:'游客',
        msgs:[],
        currentId:1,
        msgHeadImageURL:'https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=3064584167,3502823640&fm=26&gp=0.jpg',
        sendTime:'',
        chatRoomWebsocket:'',
        onlineUserList:[],
        onlineNumber:0

      }
    },
    created() {
      this.userId=parseInt(Math.random()*1000000);
      this.userName=this.userName+this.userId;
      console.log(this.userName);
      this.t = window.setInterval(this.getChatUserList, 5000);
    },
    beforeDestroy() {
      clearInterval(this.t);
    },
    destroyed() {
      this.websocketClose();
    },
    mounted(){
      console.log(this.$route.query.id);
      if (flvjs.isSupported()){
        var videoElement = document.getElementById('videoId');
        var flvPlayer = flvjs.createPlayer({
          type: 'flv',
          "isLive": true,
          url: 'http://62.234.60.111:7001/stream/'+this.$route.query.id+'.flv'
        });
        flvPlayer.attachMediaElement(videoElement);
        flvPlayer.load();
          // flvPlayer.play();
      }
  },
  methods: {       
    initWebSocket() {
      if (typeof WebSocket != "undefined") {
        this.supported = "支持 websocket";
      } else {
        this.supported = "不支持 websocket";
      }
      //ws地址
      const wsuri = "ws://62.234.60.111:8888/websocket/" + this.$route.query.id + "/" + this.type + "/" + this.userId; 
      this.chatRoomWebsocket = new WebSocket(wsuri);
      this.chatRoomWebsocket.onerror = this.websocketOnError;
      this.chatRoomWebsocket.onmessage = this.websocketOnMessage;
      this.chatRoomWebsocket.onclose = this.websocketOnClose;       
  },              
    //连接发生错误的回调方法
    websocketOnError() {
      console.log("WebSocket 连接发生错误");
    },
    //接收到消息的回调方法
    websocketOnMessage(event) {
      console.log(event);
      let data = JSON.parse(event.data);
      // this.msgHeadImageURL = data.chatImage ? data.chatImage : userPhoto;
      if (data.chatUser != this.userId&&data.chatType=='1') {
        this.msgs.push(data);
      }
      if (data.chatUser != this.userId&&data.chatType=='2') {
        this.barrageList.push({
          id: ++this.currentId,
          avatar: this.msgHeadImageURL,
          msg: data.chatContent,
          barrageStyle: "normal",
          time: 5,
          type: MESSAGE_TYPE.NORMAL,
          position: "bottom"
        });
      }
  },
    //连接关闭的回调方法
    websocketOnClose(e) {
      console.log("WebSocket 连接关闭", e);
    },
    //关闭 WebSocket 连接
    websocketClose() {
      this.chatRoomWebsocket.close();
    },       
    addToList(type) {
      if (this.sendmsg.split(" ").join("").length != 0) {
        //获取当前时间
        var time = new Date();
        this.sendTime =
        time.getHours() +
        ":" +
        (time.getMinutes() < 10
          ? "0" + time.getMinutes()
          : time.getMinutes());
        let messageData = {
          chatContent: type==2?this.userName+":"+this.sendmsg:this.sendmsg,
          chatUser: this.userId,
          chatAvatar: this.userName,
          chatImage: this.msgHeadImageURL,
          chatTime: this.sendTime,
          chatType:type
        };
        if (this.chatRoomWebsocket.readyState != "1") {
          // 如果按下按钮时不是连接状态,重连并等到连接成功再发送消息
          this.initWebSocket();
          this.chatRoomWebsocket.onopen = () => {
            this.chatRoomWebsocket.send(JSON.stringify(messageData));
            //发送消息
            if (type==1) {
              this.msgs.push({
                chatContent: this.sendmsg,
                chatUser: this.userId,
                chatAvatar: this.userName,
                chatImage: this.msgHeadImageURL,
                chatTime: this.sendTime,
              });
            }else{
              this.barrageList.push({
                id: ++this.currentId,
                avatar: this.msgHeadImageURL,
                msg: messageData.chatContent,
                barrageStyle: "normal",
                time: 5,
                type: MESSAGE_TYPE.NORMAL,
                position: "bottom"
              });
            }
            this.sendmsg = "";
        };
    } else {
      this.chatRoomWebsocket.send(JSON.stringify(messageData));
          //发送消息
          if (type==1) {
            this.msgs.push({
              chatContent: this.sendmsg,
              chatUser: this.userId,
                chatAvatar: this.userName,
                chatImage: this.msgHeadImageURL,
                chatTime: this.sendTime,
            });
          }else{
            this.barrageList.push({
              id: ++this.currentId,
              avatar: this.msgHeadImageURL,
              msg: messageData.chatContent,
              barrageStyle: "normal",
              time: 5,
              type: MESSAGE_TYPE.NORMAL,
              position: "bottom"
            });
          }
          this.sendmsg = "";
      }
  }
      // list.forEach((v) => {
      //  this.barrageList.push({
      //    id: v.id,
      //    avatar: v.avatar,
      //    msg: v.msg,
      //    time: v.time,
      //    type: MESSAGE_TYPE.NORMAL,
      //    barrageStyle: v.barrageStyle
      //  });
      // });
    },
    getChatUserList() {
      let data = {
        sid: this.$route.query.id,
        type: 1
      };
      onlineList(data).then(res => {
        // console.log(res);
        // if (res.data.state == 200) {
          this.onlineNumber = res.data.count;
          this.onlineUserList = res.data.userList;
          console.log(this.onlineNumber);
          console.log(this.onlineUserList);
        // }
      });
  },

}

}
</script>
<style  lang="less">
.barrages-drop {
  .blue {
    border-radius: 100px;
    background: #e6ff75;
    color: #fff;
  }

  .green {
    border-radius: 100px;
    background: #75ffcd;
    color: #fff;
  }
  .red {
    border-radius: 100px;
    background: #e68fba;
    color: #fff;
  }
  .yellow {
    border-radius: 100px;
    background: #dfc795;
    color: #fff;
  }
  .baberrage-stage {
    position: absolute;
    width: 100%;
    height: 212px;
    overflow: hidden;
    top: 0;
    margin-top: 130px;
  }
}
</style>

后端pom加入

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
            <version>1.3.5.RELEASE</version>
</dependency>

WebSocket配置类

package com.macro.mall.tiny.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

@Configuration
public class WebSocketConfig {
    /**
     * 注入ServerEndpointExporter,
     * 这个bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint
     * @return
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

WebSocket操作类

package com.macro.mall.tiny.controller;

import com.alibaba.druid.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

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

@RestController
@ServerEndpoint(value = "/websocket/{sid}/{type}/{userId}")   //房间号、类型(1,直播聊天)、用户Id、
@Component
public class WebSocket {

    private static WebSocket webSocket;
    private static Logger logger = LoggerFactory.getLogger(WebSocket.class);

    //当前连接数
    private static int onlineCount = 0;

    //存放每个客户端对应的MyWebSocket对象。
    private static CopyOnWriteArraySet<WebSocket> webSocketSet = new CopyOnWriteArraySet<WebSocket>();

    //与某个客户端的连接会话
    private Session session;

    //客户端唯一标识sid(直播ID)
    private String sid = "";
    //用户类型
    private Integer type=0;
    //用户ID
    private Integer userId = 0;
    //用户昵称
    private String nickName="";
    //用户头像地址
    private String headImageUrl="";

    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
    }

    @PostConstruct
    public void init() {
        webSocket = this;
    }

    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("sid") String sid,  @PathParam("type") Integer type,@PathParam("userId") Integer userId) {
        moreWindow(sid,userId,type);
        //在线数加1
        addOnlineCount();
        this.session = session;
        //加入set中
        webSocketSet.add(this);
        this.sid = sid;
        this.userId = userId;
        this.type=type;
        logger.info("用户ID:"+userId+"用户昵称:"+nickName+"新连接:sid=" + sid + " 当前在线人数" + getOnlineCount());
        try {
            sendMessage("连接成功");
        } catch (IOException e) {
            logger.error("websocket IO异常");
        }
    }

    @OnMessage
    public void onMessage(String message, Session session) {
        //群发消息
        for (WebSocket item : webSocketSet) {
            try {
                if(item.sid.equals(this.sid)){
                    item.sendMessage(message);
                    System.out.println("--------------------"+message+"总人数"+onlineCount);
                }

            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 同一用户打开多个窗口问题
     *
     * @param sid
     */
    public void moreWindow(String sid,Integer userId,Integer type) {
        if (StringUtils.isEmpty(sid)) {
            return;
        }
        if(webSocketSet.isEmpty()){
            return;
        }
        for (WebSocket item : webSocketSet) {
            if (item.sid.equals(sid)&&item.userId.equals(userId)&&item.type==type) {
                //已经有相同的了
                webSocketSet.remove(item);
                subOnlineCount();
            }
        }
    }

    /**
     * 发送消息给指定用户
     *
     * @param message
     * @param sid
     */
    public static void sendMessage(String message, @PathParam("sid") String sid) {
        logger.info("发送消息:sid=" + sid + " message:" + message);
        for (WebSocket item : webSocketSet) {
            try {
                if (sid == null) {
                    item.sendMessage(message);
                    System.out.println("+++++++++++++++"+message);
                } else if (item.sid.equals(sid)) {
                    logger.info("开始发送消息:sid=" + sid);
                    item.sendMessage(message);
                }
            } catch (IOException e) {
                logger.error("发送消息失败 sid:"+sid,e);
                continue;
            }
        }
    }

    @OnClose
    public void onClose() {
        logger.info("连接关闭:sid=" + sid + " 当前在线人数" + getOnlineCount());
        webSocketSet.remove(this);
        subOnlineCount();
    }

    @OnError
    public void onError(Session session, Throwable error) {
        error.printStackTrace();
    }

    /**
     * 当前在线人数
     * @return
     */
    public static synchronized int getOnlineCount() {
        return onlineCount;
    }

    /**
     * 添加在线人数
     */
    public static synchronized void addOnlineCount() {
        WebSocket.onlineCount++;
    }

    /**
     * 减少在线人数
     */
    public static synchronized void subOnlineCount() {
        if (WebSocket.onlineCount <= 0) {
            WebSocket.onlineCount = 0;
            return;
        }
        WebSocket.onlineCount--;
    }

    /**
     *
     * 人数列表
     */
    @PostMapping(value = "/numberList")
    public  Map numberList(String sid,Integer type,@RequestBody Map data){
        sid=data.get("sid").toString();
        type=1;
        Map map=new HashMap<>();
        List<String> userList=new ArrayList<>();
        Integer count=0;
        for (WebSocket item : webSocketSet) {
            System.out.println(item.sid);
            if(item.sid!=null&&item.sid.equals(sid)){
                userList.add("游客"+item.userId.toString());
                count++;
            }
        }
        map.put("userList",userList);
        map.put("count",count);
        System.out.println(map.toString());
        return map;
    }
}



上线的话,解决跨域问题 可以用nginx反向代理 也可以在后端加个配置
跨域配置

package com.macro.mall.tiny.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;


/**
 * Author:js
 * Date:2019/
 */
@Configuration
@EnableWebMvc
public class CorsConfig extends WebMvcConfigurerAdapter {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**");
    }
}


带websocket的项目打包会报错 需要在pom加入

<plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <skip>true</skip>
                </configuration>
            </plugin>

这些做完之后,就已经完成了。
Docker部署 直播实时弹幕+聊天室 vue+websocket+spring-boot
测试地址 后面的id相当于一个房间号 也是推流的文件名
http://62.234.60.111/login?id=test