1.优化websocket接口,新增赔率参数

This commit is contained in:
2026-05-15 16:24:19 +08:00
parent 575aa279bd
commit 91229f4477
13 changed files with 434 additions and 40 deletions

View File

@@ -1,3 +1,3 @@
export default {
tip: 'WebSocket connection test for status stream: listen to period.tick / period.opened events.',
tip: 'WebSocket test: load config then connect to auto-subscribe. Period state via period.tick; player odds via user.streak / wallet.changed / bet.accepted (no game.config full table).',
}

View File

@@ -12,5 +12,12 @@ export default {
btn_clear: 'Clear log',
log_title: 'WebSocket log',
log_empty: 'No logs yet. Connect first and then send a message.',
subscribe_topics: 'Auto-subscribe topics',
odds_push_topics: 'Odds push topics',
player_odds_fields: 'Player odds fields',
test_player_odds: 'Test player odds (config preview)',
test_player_odds_hint: 'After connect and subscribe, the server pushes demo frames with is_test / preview (from highest-streak sample user in DB).',
test_source_db: 'Sample user from DB',
test_source_synthetic: 'Synthetic demo',
}

View File

@@ -1,3 +1,3 @@
export default {
tip: 'WebSocket 连接测试(状态流):按文档监听 period.tick / period.opened 等事件。',
tip: 'WebSocket 联调:加载配置后连接即自动订阅;对局状态见 period.tick当前玩家赔率见 user.streak / wallet.changed / bet.accepted不含 game.config 全表)。',
}

View File

@@ -12,5 +12,12 @@ export default {
btn_clear: '清空日志',
log_title: 'WebSocket 日志',
log_empty: '暂无日志,请先连接后发送消息。',
subscribe_topics: '自动订阅主题',
odds_push_topics: '赔率推送主题',
player_odds_fields: '玩家赔率字段',
test_player_odds: '测试玩家赔率(配置预览)',
test_player_odds_hint: '连接并订阅赔率主题后,服务端将推送带 is_test / preview 的演示帧(数据来自库内连胜最高的样例玩家)。',
test_source_db: '库内样例玩家',
test_source_synthetic: '合成演示',
}

View File

@@ -23,6 +23,38 @@
</el-form-item>
</el-form>
<div class="text-muted mb-8">{{ connectTip || '-' }}</div>
<template v-if="ready && subscribeTopics.length">
<div class="mb-4">
<span class="config-label">{{ t('test.ws.subscribe_topics') }}</span>
<el-tag v-for="topic in subscribeTopics" :key="topic" size="small" class="mr-4 mb-4">{{ topic }}</el-tag>
</div>
<div v-if="oddsPushTopics.length" class="mb-4">
<span class="config-label">{{ t('test.ws.odds_push_topics') }}</span>
<el-tag v-for="topic in oddsPushTopics" :key="'odds-' + topic" type="warning" size="small" class="mr-4 mb-4">{{ topic }}</el-tag>
</div>
<div v-if="playerOddsFields.length" class="mb-4">
<span class="config-label">{{ t('test.ws.player_odds_fields') }}</span>
<el-tag v-for="field in playerOddsFields" :key="field" type="success" size="small" class="mr-4 mb-4">{{ field }}</el-tag>
</div>
</template>
<el-card v-if="testPlayerOdds" shadow="never" class="test-odds-card">
<template #header>
<span>{{ t('test.ws.test_player_odds') }}</span>
<el-tag type="info" size="small" class="ml-8">{{ testPlayerOddsSourceLabel }}</el-tag>
</template>
<p class="text-muted mb-8">{{ t('test.ws.test_player_odds_hint') }}</p>
<el-descriptions :column="2" border size="small">
<el-descriptions-item label="user_id">{{ testPlayerOdds.user_id }}</el-descriptions-item>
<el-descriptions-item label="username">{{ testPlayerOdds.username || '-' }}</el-descriptions-item>
<el-descriptions-item label="uuid">{{ testPlayerOdds.uuid || '-' }}</el-descriptions-item>
<el-descriptions-item label="phone">{{ testPlayerOdds.phone || '-' }}</el-descriptions-item>
<el-descriptions-item label="coin">{{ testPlayerOdds.coin }}</el-descriptions-item>
<el-descriptions-item label="current_streak">{{ testPlayerOdds.current_streak }}</el-descriptions-item>
<el-descriptions-item label="streak_level">{{ testPlayerOdds.streak_level }}</el-descriptions-item>
<el-descriptions-item label="odds_factor">{{ testPlayerOdds.odds_factor }}</el-descriptions-item>
<el-descriptions-item label="is_jackpot">{{ testPlayerOdds.is_jackpot ? 'true' : 'false' }}</el-descriptions-item>
</el-descriptions>
</el-card>
</el-card>
<el-card shadow="never">
@@ -35,7 +67,7 @@
</template>
<script setup lang="ts">
import { computed, onUnmounted, ref } from 'vue'
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import createAxios from '/@/utils/axios'
@@ -58,11 +90,15 @@ const connectTip = ref('')
const sendMessage = ref('')
/** 连接成功后自动订阅(由 wsConfig.subscribe_topics 下发) */
const subscribeTopics = ref<string[]>([])
const oddsPushTopics = ref<string[]>([])
const playerOddsFields = ref<string[]>([])
const testPlayerOdds = ref<Record<string, unknown> | null>(null)
const ws = ref<WebSocket | null>(null)
const logs = ref<Array<{ t: number; event: string; payload: string }>>([])
const defaultSubscribeTopics = [
'period.tick',
'user.streak',
'period.opened',
'period.locked',
'period.payout',
@@ -71,6 +107,17 @@ const defaultSubscribeTopics = [
'auto.spin.progress',
] as const
const testPlayerOddsSourceLabel = computed(() => {
const source = testPlayerOdds.value?.source
if (source === 'db_user') {
return t('test.ws.test_source_db')
}
if (source === 'synthetic') {
return t('test.ws.test_source_synthetic')
}
return ''
})
const logText = computed(() => {
if (!logs.value.length) return t('test.ws.log_empty')
return logs.value
@@ -109,6 +156,20 @@ async function loadConfig() {
} else {
subscribeTopics.value = [...defaultSubscribeTopics]
}
const rawOddsTopics = res.data.odds_push_topics
if (Array.isArray(rawOddsTopics)) {
oddsPushTopics.value = rawOddsTopics.filter((x: unknown): x is string => typeof x === 'string' && x.trim() !== '')
} else {
oddsPushTopics.value = ['user.streak', 'wallet.changed', 'bet.accepted']
}
const rawOddsFields = res.data.player_odds_fields
if (Array.isArray(rawOddsFields)) {
playerOddsFields.value = rawOddsFields.filter((x: unknown): x is string => typeof x === 'string' && x.trim() !== '')
} else {
playerOddsFields.value = ['current_streak', 'streak_level', 'odds_factor', 'is_jackpot']
}
const rawTestOdds = res.data.test_player_odds
testPlayerOdds.value = rawTestOdds && typeof rawTestOdds === 'object' ? (rawTestOdds as Record<string, unknown>) : null
const firstSample = Array.isArray(res.data.sample_messages) && res.data.sample_messages.length ? String(res.data.sample_messages[0]) : ''
sendMessage.value = firstSample
ready.value = wsUrl.value !== ''
@@ -146,10 +207,22 @@ function connectWs() {
})
}
socket.onmessage = (event) => {
const raw = typeof event.data === 'string' ? event.data : JSON.stringify(event.data)
let eventName = 'ws.message'
try {
const parsed = JSON.parse(raw) as { event?: string; topic?: string }
if (typeof parsed.event === 'string' && parsed.event !== '') {
eventName = parsed.event
} else if (typeof parsed.topic === 'string' && parsed.topic !== '') {
eventName = parsed.topic
}
} catch {
// keep default event name
}
appendLog({
t: Date.now(),
event: 'ws.message',
payload: typeof event.data === 'string' ? event.data : JSON.stringify(event.data),
event: eventName,
payload: raw,
})
}
socket.onerror = () => {
@@ -199,6 +272,10 @@ function sendWs() {
}
}
onMounted(() => {
void loadConfig()
})
onUnmounted(() => {
disconnectWs()
clearLogs()
@@ -217,4 +294,21 @@ onUnmounted(() => {
color: var(--el-text-color-secondary);
font-size: 13px;
}
.config-label {
color: var(--el-text-color-regular);
font-size: 13px;
margin-right: 8px;
}
.mr-4 {
margin-right: 4px;
}
.mb-4 {
margin-bottom: 4px;
}
.test-odds-card {
margin-top: 12px;
}
.ml-8 {
margin-left: 8px;
}
</style>