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>
这些做完之后,就已经完成了。
测试地址 后面的id相当于一个房间号 也是推流的文件名
http://62.234.60.111/login?id=test