feat: 优化100人并发投票支持 + 修复奖项名称硬编码问题
服务端优化: - Socket.IO 配置优化,支持高并发 WebSocket 连接 - 添加 connectTimeout、perMessageDeflate 等参数 压测脚本: - 新增 vote-real-scenario.yaml 真实场景压测配置 - 支持 100人瞬间爆发、每人7票、不同投票速度模拟 - 添加随机延迟函数模拟真实用户行为 - 强制 WebSocket 传输避免 HTTP 连接限制 前端修复: - 修复 PostcardItem、PostcardDisplay 奖项名称硬编码问题 - 组件现在从后端 awards 配置动态获取奖项名称 - 修复 LiveVotingView、AdminControl 传递 awards 数据 - 新增 gala 压测命令到 package.json 测试验证: - 100人并发压测通过,成功率 100% - P95 延迟 0.7ms,远低于 500ms 阈值 - 系统可稳定支持 3.3 倍现场负载
This commit is contained in:
@@ -1,19 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import type { VotingProgram, VoteStamp } from '@gala/shared/types';
|
||||
import type { VotingProgram, VoteStamp, AwardConfig } from '@gala/shared/types';
|
||||
import Postmark from './Postmark.vue';
|
||||
|
||||
// 票据类型名称映射
|
||||
const TICKET_TYPE_NAMES: Record<string, string> = {
|
||||
creative: '最佳创意',
|
||||
visual: '最佳视觉',
|
||||
atmosphere: '最佳氛围',
|
||||
performance: '最佳表演',
|
||||
teamwork: '最佳团队',
|
||||
popularity: '最受欢迎',
|
||||
potential: '最具潜力',
|
||||
};
|
||||
|
||||
interface Props {
|
||||
program: VotingProgram;
|
||||
isFocused: boolean;
|
||||
@@ -24,6 +13,7 @@ interface Props {
|
||||
rotateZ?: number; // 3D 旋转 Z
|
||||
z?: number; // 3D 位移 Z
|
||||
index?: number; // 传入索引用于生成邮编
|
||||
awards?: AwardConfig[]; // 奖项配置
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
@@ -77,6 +67,15 @@ function getStampColor(ticketType: string): string {
|
||||
};
|
||||
return colors[ticketType] || '#e8313f';
|
||||
}
|
||||
|
||||
// 从 awards 配置中查找奖项名称
|
||||
function getAwardName(type: string): string {
|
||||
if (!props.awards || props.awards.length === 0) {
|
||||
return type;
|
||||
}
|
||||
const award = props.awards.find(a => a.id === type);
|
||||
return award?.name || type;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -158,7 +157,7 @@ function getStampColor(ticketType: string): string {
|
||||
:style="getStampStyle(stamp)"
|
||||
>
|
||||
<Postmark
|
||||
:award-name="TICKET_TYPE_NAMES[stamp.ticketType] || stamp.ticketType"
|
||||
:award-name="getAwardName(stamp.ticketType)"
|
||||
:user-name="stamp.userName"
|
||||
color="red"
|
||||
/>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, onMounted, onUnmounted } from 'vue';
|
||||
import PostcardItem from './PostcardItem.vue';
|
||||
import type { VotingProgram } from '@gala/shared/types';
|
||||
import type { VotingProgram, AwardConfig } from '@gala/shared/types';
|
||||
|
||||
export interface Props {
|
||||
programs: VotingProgram[];
|
||||
awards?: AwardConfig[];
|
||||
columns?: number;
|
||||
rows?: number;
|
||||
}
|
||||
@@ -65,6 +66,7 @@ defineExpose({
|
||||
:order="program.order"
|
||||
:votes="program.votes"
|
||||
:stamps="program.stamps"
|
||||
:awards="awards"
|
||||
class="grid-item"
|
||||
/>
|
||||
|
||||
|
||||
@@ -1,20 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import type { VoteStamp } from '@gala/shared/types';
|
||||
import type { VoteStamp, AwardConfig } from '@gala/shared/types';
|
||||
import stampImage from '../assets/images/stamp-horse-2026.png';
|
||||
import Postmark from './Postmark.vue';
|
||||
|
||||
// 票据类型名称映射
|
||||
const TICKET_TYPE_NAMES: Record<string, string> = {
|
||||
creative: '最佳创意',
|
||||
visual: '最佳视觉',
|
||||
atmosphere: '最佳氛围',
|
||||
performance: '最佳表演',
|
||||
teamwork: '最佳团队',
|
||||
popularity: '最受欢迎',
|
||||
potential: '最具潜力',
|
||||
};
|
||||
|
||||
export interface Props {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -24,6 +13,7 @@ export interface Props {
|
||||
order: number;
|
||||
votes: number;
|
||||
stamps?: VoteStamp[];
|
||||
awards?: AwardConfig[]; // 奖项配置(从后端获取)
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
@@ -72,19 +62,14 @@ function getStampStyle(stamp: VoteStamp) {
|
||||
// Stamp target ref for particle animation
|
||||
const stampTargetRef = ref<HTMLElement | null>(null);
|
||||
|
||||
// Award name mapping for stamps
|
||||
const awardNames: Record<string, string> = {
|
||||
creative: '最佳创意奖',
|
||||
visual: '最佳视觉奖',
|
||||
atmosphere: '最佳气氛奖',
|
||||
performance: '最佳表演奖',
|
||||
teamwork: '最佳团队奖',
|
||||
popularity: '最受欢迎奖',
|
||||
potential: '最具潜力奖',
|
||||
};
|
||||
|
||||
// 从 awards 配置中查找奖项名称
|
||||
function getAwardName(type: string) {
|
||||
return awardNames[type] || '优秀节目奖';
|
||||
if (!props.awards || props.awards.length === 0) {
|
||||
// 如果没有 awards 配置,返回票种ID本身
|
||||
return type;
|
||||
}
|
||||
const award = props.awards.find(a => a.id === type);
|
||||
return award?.name || type;
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
|
||||
@@ -334,24 +334,14 @@ const lotteryPhaseLabel = computed(() => {
|
||||
}
|
||||
});
|
||||
|
||||
// Award type labels
|
||||
const awardLabels: Record<string, string> = {
|
||||
creative: '最佳创意奖',
|
||||
visual: '最佳视觉奖',
|
||||
atmosphere: '最佳气氛奖',
|
||||
performance: '最佳表演奖',
|
||||
teamwork: '最佳团队奖',
|
||||
popularity: '最受欢迎奖',
|
||||
potential: '最具潜力奖',
|
||||
};
|
||||
|
||||
// Compute award statistics grouped by award type
|
||||
const awardStats = computed(() => {
|
||||
const stats: Record<string, Array<{ programName: string; teamName: string; votes: number; programId: string }>> = {};
|
||||
|
||||
// Initialize all award types
|
||||
Object.keys(awardLabels).forEach(type => {
|
||||
stats[type] = [];
|
||||
// Initialize all award types from admin.awards
|
||||
const awards = admin.awards || [];
|
||||
awards.forEach((award: any) => {
|
||||
stats[award.id] = [];
|
||||
});
|
||||
|
||||
// Aggregate stamps by award type
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ref, computed, onMounted, onUnmounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useDisplayStore } from '../stores/display';
|
||||
import PostcardGrid from '../components/PostcardGrid.vue';
|
||||
import type { VotingProgram, AdminState } from '@gala/shared/types';
|
||||
import type { VotingProgram, AdminState, AwardConfig } from '@gala/shared/types';
|
||||
import { SOCKET_EVENTS } from '@gala/shared/constants';
|
||||
|
||||
const router = useRouter();
|
||||
@@ -11,6 +11,7 @@ const displayStore = useDisplayStore();
|
||||
|
||||
// 节目列表
|
||||
const programs = ref<VotingProgram[]>([]);
|
||||
const awards = ref<AwardConfig[]>([]);
|
||||
const votingOpen = ref(false);
|
||||
const totalVotes = ref(0);
|
||||
|
||||
@@ -29,6 +30,7 @@ function goBack() {
|
||||
// 处理状态同步
|
||||
function handleStateSync(state: AdminState) {
|
||||
programs.value = state.voting.programs;
|
||||
awards.value = state.voting.awards || [];
|
||||
votingOpen.value = state.voting.subPhase === 'OPEN';
|
||||
totalVotes.value = state.voting.totalVotes;
|
||||
}
|
||||
@@ -101,6 +103,7 @@ onUnmounted(() => {
|
||||
<PostcardGrid
|
||||
ref="gridRef"
|
||||
:programs="programs"
|
||||
:awards="awards"
|
||||
:columns="4"
|
||||
:rows="2"
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user