init commit

This commit is contained in:
2024-05-08 18:01:01 +08:00
commit 1eed1428f2
20 changed files with 4632 additions and 0 deletions

229
src/App.vue Normal file
View File

@@ -0,0 +1,229 @@
<script setup lang="ts">
// This starter template is using Vue 3 <script setup> SFCs
// Check out https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup
import { ref } from "vue"
import axios from "axios"
import { createSocket, destroySocket } from "./socket/index"
import { createCoyoteSocket, closeCoyoteSocket, qrcodeSrc, qrcodeShow } from "./socket/coyote"
import { Notyf } from 'notyf'
const notyf = new Notyf({ duration: 3000 })
// API
const api = axios.create({
baseURL: "http://localhost:3000",
})
// 替换你的主播身份码
const codeId = ref("")
// 替换你的app应用 [这里测试为互动游戏]
const appId = ref("")
// [向 node server请求接口后自动返回]
const gameId = ref("")
// v2改为server response 服务器返回websocket信息而非手动获取
const authBody = ref("")
const wssLinks = ref([])
// heartBeat Timer
const heartBeatTimer = ref<NodeJS.Timer>()
// be ready
clearInterval(heartBeatTimer.value!)
/**
* 测试请求鉴权接口
*/
const getAuth = () => {
api.post("/getAuth", {})
.then(({ data }) => {
console.log("-----鉴权成功-----")
notyf.success({ message: "鉴权成功" })
})
.catch((err) => {
console.log("-----鉴权失败-----")
notyf.error({ message: "鉴权失败" })
})
}
const heartBeatThis = (game_id) => {
// 心跳 是否成功
api.post("/gameHeartBeat", {
game_id,
})
.then(({ data }) => {
console.log("-----心跳成功-----")
console.log("返回:", data)
})
.catch((err) => {
console.log("-----心跳失败-----")
})
}
/**
* @comment 注意所有的接口基于鉴权成功后才能正确返回
* 测试请求游戏开启接口
*/
const gameStart = () => {
api.post("/gameStart", {
code: codeId.value,
app_id: Number(appId.value),
})
.then(({ data }) => {
if (data.code === 0) {
const res = data.data
const { game_info, websocket_info } = res
const { auth_body, wss_link } = websocket_info
authBody.value = auth_body
wssLinks.value = wss_link
console.log("-----游戏开始成功-----")
console.log("返回GameId", game_info)
notyf.success({ message: "游戏开始成功" })
gameId.value = game_info.game_id
// v2改为20s请求心跳一次不然60s会自动关闭
heartBeatTimer.value = setInterval(() => {
heartBeatThis(game_info.game_id)
}, 20000)
handleCreateSocket()
} else {
console.log("-----游戏开始失败-----")
console.log("原因:", data)
notyf.error({ message: "游戏开始失败" })
}
})
.catch((err) => {
console.log("-----游戏开始失败-----")
console.log(err)
notyf.error({ message: "游戏开始失败" })
})
}
/**
* @comment 基于gameStart成功后才会关闭正常否则获取不到game_id
* 测试请求游戏关闭接口
*/
const gameEnd = () => {
api.post("/gameEnd", {
game_id: gameId.value,
app_id: Number(appId.value),
})
.then(({ data }) => {
if (data.code === 0) {
console.log("-----游戏关闭成功-----")
console.log("返回:", data)
// 清空长链
authBody.value = ""
wssLinks.value = []
clearInterval(heartBeatTimer.value!) // 这里会报个错误,加了个非空断言
handleDestroySocket()
console.log("-----心跳关闭成功-----")
notyf.success({ message: "游戏关闭成功" })
} else {
console.log("-----游戏关闭失败-----")
console.log("原因:", data)
notyf.error({ message: "游戏关闭失败" })
}
})
.catch((err) => {
console.log("-----游戏关闭失败-----")
console.log(err)
notyf.error({ message: "游戏关闭失败" })
})
}
/**
* 测试创建长长连接接口
*/
const handleCreateSocket = () => {
if (authBody.value && wssLinks.value) {
createSocket(authBody.value, wssLinks.value)
console.log("-----长连接创建成功-----")
}
}
/**
* 测试销毁长长连接接口
*/
const handleDestroySocket = () => {
destroySocket()
console.log("-----长连接销毁成功-----")
}
/**
* 测试按钮
*/
const test = () => {
let map = {
"123": "AAWWWWW",
"123456": "aaaaaaaa",
}
notyf.success({ message: '测试吐司', duration: 3000, ripple: true })
notyf.success({ message: map[123], duration: 3000, ripple: true })
}
/**
* 显示二维码
*/
const showqrcode = () => {
qrcodeShow.value = true;
}
/**
* 隐藏二维码
*/
const hideqrcode = () => {
qrcodeShow.value = false;
}
</script>
<template>
<div id="qrcode-overlay" v-show="qrcodeShow">
<div id="qrcode-container">
<div id="qrcode-text">
<p>使用DG-LAB APP扫码建立WebSocket链接</p>
<p>请先开始游戏与直播间建立连接</p>
</div>
<div id="qrcode-img">
<img id="qrcode" :src="qrcodeSrc">
</div>
<div class="close-qrcode" @click="hideqrcode">关闭</div>
</div>
</div>
<div>
<h2>游戏设置</h2>
<div class="form">
<label>主播身份码</label>
<input type="password" placeholder="填写主播身份码" v-model="codeId"/>
<label>app_id</label>
<input type="text" placeholder="填写 app_id" v-model="appId" />
<button @click="gameStart">游戏开始</button>
<button @click="gameEnd">游戏结束</button>
<button @click="createCoyoteSocket">连接郊狼</button>
<button @click="closeCoyoteSocket">断开郊狼</button>
</div>
<hr />
<h2>调试选项</h2>
<div class="form">
<button @click="getAuth">鉴权</button>
<button @click="test">测试</button>
</div>
</div>
</template>
<style>
#app {
color: #fff;
margin: 60px 50px;
}
.form {
display: flex;
flex-direction: column;
}
.form input,
.form button {
width: 300px;
height: 50px;
margin: 10px 0;
font-size: 18px;
}
</style>

8
src/assets/danmaku-websocket.min.js vendored Normal file

File diff suppressed because one or more lines are too long

19
src/assets/dataMap.ts Normal file
View File

@@ -0,0 +1,19 @@
// Desc: 用于存放数据映射表
// 礼物对应的波形
const waveData = {
"31037": `["0A0A0A0A00000000","0A0A0A0A0A0A0A0A","0A0A0A0A14141414","0A0A0A0A1E1E1E1E","0A0A0A0A28282828","0A0A0A0A32323232","0A0A0A0A3C3C3C3C","0A0A0A0A46464646","0A0A0A0A50505050","0A0A0A0A5A5A5A5A","0A0A0A0A64646464"]`,
"31164": `["0A0A0A0A00000000","0D0D0D0D0F0F0F0F","101010101E1E1E1E","1313131332323232","1616161641414141","1A1A1A1A50505050","1D1D1D1D64646464","202020205A5A5A5A","2323232350505050","262626264B4B4B4B","2A2A2A2A41414141"]`,
"32609": `["4A4A4A4A64646464","4545454564646464","4040404064646464","3B3B3B3B64646464","3636363664646464","3232323264646464","2D2D2D2D64646464","2828282864646464","2323232364646464","1E1E1E1E64646464","1A1A1A1A64646464"]`
}
// 礼物id和礼物名的对应关系
const giftData = {
"31036": "小花花",
"31039": "牛哇牛哇",
"32609": "棒棒糖",
"31037": "打call",
"31164": "粉丝团灯牌",
}
export { waveData }

1
src/assets/notyf.min.css vendored Normal file

File diff suppressed because one or more lines are too long

9
src/env.d.ts vendored Normal file
View File

@@ -0,0 +1,9 @@
/// <reference types="vite/client" />
declare module '*.vue' {
import type { DefineComponent } from 'vue'
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
const component: DefineComponent<{}, {}, any>
export default component
}

5
src/main.ts Normal file
View File

@@ -0,0 +1,5 @@
import { createApp } from "vue"
import App from "./App.vue"
createApp(App).mount("#app")

171
src/socket/coyote.ts Normal file
View File

@@ -0,0 +1,171 @@
import {ref} from "vue";
import { Notyf } from 'notyf'
import { waveData } from "../assets/dataMap";
const notyf = new Notyf({ duration: 4000 })
const QRCode = require('qrcode')
let channelAStrength = 0; // A通道强度
let channelBStrength = 0; // B通道强度
let connectionId = ""; // 从接口获取的连接标识符
let targetWSId = ""; // 发送目标
let fangdou = 500; //500毫秒防抖
let fangdouSetTimeOut; // 防抖定时器
let followAStrength = false; //跟随AB软上限
let followBStrength = false;
let wsConn; // 全局ws链接
const feedBackMsg = {
"feedback-0": "A通道○",
"feedback-1": "A通道△",
"feedback-2": "A通道□",
"feedback-3": "A通道☆",
"feedback-4": "A通道⬡",
"feedback-5": "B通道○",
"feedback-6": "B通道△",
"feedback-7": "B通道□",
"feedback-8": "B通道☆",
"feedback-9": "B通道⬡",
}
const qrcodeSrc = ref("")
const qrcodeShow = ref(false)
QRCode.toDataURL("https://www.dungeon-lab.com/app-download.php#DGLAB-SOCKET#ws://39.108.168.199:9999/", function (err, url) {
//console.log(url)
qrcodeSrc.value = url
})
function createCoyoteSocket() {
wsConn = new WebSocket('ws://coyote.babyfang.cn:9999/');
wsConn.onopen = function (event) {
console.log("WebSocket连接已建立");
}
wsConn.onmessage = function (event) {
let msg = {} as any
try {
msg = JSON.parse(event.data);
} catch (e) {
console.log(event.data);
return;
}
console.log(event.data)
switch (msg.type) {
case 'bind':
if (!msg.targetId) {
connectionId = msg.clientId;
console.log(`收到clientId${connectionId}`);
QRCode.toDataURL("https://www.dungeon-lab.com/app-download.php#DGLAB-SOCKET#ws://coyote.babyfang.cn:9999/" + connectionId, function (err, url) {
//console.log(url)
qrcodeSrc.value = url
})
qrcodeShow.value = true
} else {
if (msg.clientId != connectionId) {
console.log("错误的clientId")
return;
}
targetWSId = msg.targetId;
console.log("收到targetId: " + msg.targetId + ", msg: " + msg.message);
qrcodeShow.value = false
notyf.success({message: "郊狼连接成功"})
}
break;
case 'break':
if (msg.targetId != targetWSId)
return;
console.log("收到断开连接指令")
notyf.error({ message: "收到断开连接指令" })
//location.reload();
break;
case 'error':
if (msg.targetId != targetWSId)
return;
console.log("对方已断开code" + msg.message)
notyf.error({ message: "对方已断开(" + msg.message + "" })
break;
case 'msg':
const result: { type: string; numbers: number[] }[] = []
if(msg.message.includes("strength")) {
const numbers = msg.message.match(/\d+/g).map(Number)
result.push({ type: "strength", numbers: numbers })
}
}
}
}
function sendWsMsg(messageObj) {
messageObj.clientId = connectionId;
messageObj.targetId = targetWSId;
if (!messageObj.hasOwnProperty('type'))
messageObj.type = "msg";
wsConn.send(JSON.stringify((messageObj)));
}
function addOrIncrease(type, channelIndex, strength) {
// 1 减少一 2 增加一 3 设置到
// channel:1-A 2-B
// 获取当前频道元素和当前值
let channelStrength = channelIndex === 1 ? channelAStrength : channelBStrength;
// 如果是设置操作
if (type === 3) {
channelStrength = strength; //固定为0
}
// 减少一
else if (type === 1) {
channelStrength = Math.max(channelStrength - strength, 0);
}
// 增加一
else if (type === 2) {
channelStrength = Math.min(channelStrength + strength, 200);
}
// 构造消息对象并发送
const data = { type, strength: channelStrength, message: "set channel", channel: channelIndex };
console.log(data)
sendWsMsg(data);
}
function clearAB(channelIndex) {
const data = { type: 4, message: "clear-" + channelIndex }
sendWsMsg(data);
}
function sendWaveData(timeA, timeB, waveA, waveB) {
if (fangdouSetTimeOut) {
return
}
const msg1 = `A:${waveData[waveA]}`
const msg2 = `B:${waveData[waveB]}`
const data = {
type: "clientMsg", message: msg1, message2: msg2, time1: timeA, time2: timeB
}
sendWsMsg(data)
fangdouSetTimeOut = setTimeout(() => {
clearTimeout(fangdouSetTimeOut)
fangdouSetTimeOut = null
}, fangdou)
}
function closeCoyoteSocket() {
try {
wsConn.close()
}
catch (e) {
notyf.error( {message: "郊狼连接断开失败"} )
console.log(e)
return
}
wsConn = null
notyf.success( {message: "郊狼连接已断开"} )
}
export { createCoyoteSocket, closeCoyoteSocket, sendWsMsg, sendWaveData, addOrIncrease, clearAB, qrcodeSrc, qrcodeShow, channelAStrength, channelBStrength }

133
src/socket/index.ts Normal file
View File

@@ -0,0 +1,133 @@
import DanmakuWebSocket from "../assets/danmaku-websocket.min.js"
import { Notyf } from 'notyf'
import { closeCoyoteSocket, addOrIncrease, sendWaveData } from "./coyote"
let ws: DanmakuWebSocket
const notyf = new Notyf({ duration: 4000 })
/**
* 创建socket长连接
* @param authBody
* @param wssLinks
*/
function createSocket(authBody: string, wssLinks: string[]) {
const opt = {
...getWebSocketConfig(authBody, wssLinks),
// 收到消息,
onReceivedMessage: (res) => {
console.log("收到"+ res.cmd +"消息:")
//console.log(res.data.uname + "(大航海" +res.data.guard_level + "级):" + res.data.msg)
// if (res.data.msg == "#UPA1") {
// try {
// addOrIncrease(2, 1, 1)
// notyf.success("A通道强度增加成功")
// }
// catch (e) {
// console.log(e)
// notyf.error("A通道强度增加失败")
// }
// }
if (res.cmd == "LIVE_OPEN_PLATFORM_SEND_GIFT") {
if (res.data.gift_id == 31036) {
// 小花花减强度1
try {
addOrIncrease(1, 1, 1)
addOrIncrease(1, 2, 1)
notyf.success("收到花花,强度-1")
}
catch (e) {
console.log(e)
notyf.error("强度操作失败!")
}
} else if (res.data.gift_id == 31039) {
// 牛哇牛哇加强度1
try {
addOrIncrease(2, 1, 1)
addOrIncrease(2, 2, 1)
notyf.success("收到牛牛,强度+1")
}
catch (e) {
console.log(e)
notyf.error("强度操作失败!")
}
} else {
// 其他礼物,发送波形数据
try {
sendWaveData(5, 5, res.data.gift_id, res.data.gift_id)
notyf.success("收到礼物" + res.data.gift_name)
}
catch (e) {
console.log(e)
notyf.error("发送波形数据失败!")
}
}
}
console.log(res)
},
// 收到心跳处理回调
onHeartBeatReply: (data) => console.log("收到心跳处理回调:", data),
onError: (data) => console.log("error", data),
onListConnectError: () => {
console.log("list connect error")
destroySocket()
},
}
if (!ws) {
ws = new DanmakuWebSocket(opt)
}
return ws
}
/**
* 获取websocket配置信息
* @param authBody
* @param wssLinks
*/
function getWebSocketConfig(authBody: string, wssLinks: string[]) {
const url = wssLinks[0]
const urlList = wssLinks
const auth_body = JSON.parse(authBody)
return {
url,
urlList,
customAuthParam: [
{
key: "key",
value: auth_body.key,
type: "string",
},
{
key: "group",
value: auth_body.group,
type: "string",
},
],
rid: auth_body.roomid,
protover: auth_body.protoover,
uid: auth_body.uid,
}
}
/**
* 销毁websocket
*/
function destroySocket() {
console.log("destroy1")
ws && ws.destroy()
ws = undefined
console.log("destroy2")
}
/**
* 获取websocket实例
*/
function getWsClient() {
return ws
}
export { createSocket, destroySocket, getWebSocketConfig, getWsClient }

19
src/types/index.d.ts vendored Normal file
View File

@@ -0,0 +1,19 @@
declare interface ISocketData {
// ip地址
ip: number[]
// host地址 可能是ip 也可能是域名。
host: string[]
// 长连使用的请求json体 第三方无需关注内容,建立长连时使用即可。
auth_body: string
// tcp 端口号
tcp_port: number[]
// ws 端口号
ws_port: number[]
// wss 端口号
wss_port: number[]
}