技术实现思路
Vue 3 + TypeScript 作为前端主框架具有以下优势: 1. 响应式系统改进:Vue 3 的 Composition API 提供了更灵活的状态管理方式,特别适合复杂的 Ban/Pick 状态机管理 2. 类型安全:TypeScript 提供强类型支持,减少运行时错误,特别适合团队协作开发 3. 移动端适配:Vue 3 的响应式特性可以很好地适配...
技术栈推荐
前端技术栈
主框架选择
Vue 3 + TypeScript 作为前端主框架具有以下优势:
- 响应式系统改进:Vue 3 的 Composition API 提供了更灵活的状态管理方式,特别适合复杂的 Ban/Pick 状态机管理
- 类型安全:TypeScript 提供强类型支持,减少运行时错误,特别适合团队协作开发
- 移动端适配:Vue 3 的响应式特性可以很好地适配不同屏幕尺寸,配合 CSS 媒体查询实现响应式布局
- 生态丰富:Vue 生态系统中有大量现成的动画库(如 Vue2-animate)、UI 组件库(如 Element Plus Mobile)
React 也是一个不错的选择,但考虑到项目需要快速开发和维护,Vue 3 的学习曲线相对平缓,社区中文资源更丰富。在移动端方面,React Native 可能不如 Vue 配合 Vite 构建的 H5 应用来得轻量。
推荐:Vue 3 + TypeScript
状态管理
Pinia + Composables 组合作为状态管理方案:
- Pinia:Vue 官方推荐的状态管理库,比 Vuex 更轻量、更易于理解,支持 TypeScript,提供了更好的 DevTools 集成
- Composables:Vue 3 的组合式函数,可以将 Ban/Pick 相关的状态逻辑封装为可复用的函数,提高代码复用性
具体的状态管理设计包括:
- 房间状态:管理房间信息、用户身份、连接状态
- 选人状态:管理当前阶段、剩余时间、已选球员、已 Ban 球员
- UI 状态:管理界面显示、动画效果、倒计时等
推荐:Pinia + Composables
动画实现
Framer Motion 或 GSAP 配合 CSS 动画:
- Framer Motion:功能强大的动画库,支持手势操作、物理动画,API 直观易用
- GSAP:专业的动画库,性能优异,适合复杂的动画序列
对于 Ban/Pick 系统的动画需求,建议使用 GSAP 处理复杂的界面切换动画,CSS 动画处理简单的交互反馈,使用 Lottie 处理复杂的矢量动画(如倒计时效果)。
推荐:GSAP + CSS 动画
WebSocket 客户端
Socket.IO 客户端:
- 支持自动重连和心跳机制
- 提供房间广播功能,方便选人状态同步
- 支持命名空间和房间隔离
- 有完善的错误处理和降级机制
推荐:Socket.IO 客户端
构建工具
Vite:
- 开发环境启动速度快,热更新性能好
- 对 TypeScript、CSS 预处理器支持完善
- 拆包和代码优化能力强,适合 H5 应用
- 生态活跃,插件丰富
推荐:Vite
后端技术栈
主框架选择
Node.js + Express 或 NestJS:
- Node.js + Express:轻量、灵活,适合小型到中型项目,开发效率高
- NestJS:基于 Express 的企业级框架,提供了更好的代码组织和架构模式,适合大型项目
考虑到项目的实时性要求,推荐使用 NestJS,它内置了 WebSocket 支持,提供了更好的 TypeScript 支持和依赖注入机制,便于后续功能扩展。
推荐:NestJS
WebSocket 服务
Socket.IO 服务端:
- 与客户端保持一致,便于开发和维护
- 提供强大的房间管理和广播功能
- 支持中间件和认证机制
- 有完善的文档和社区支持
推荐:Socket.IO 服务端
数据存储
Redis + 内存存储:
- Redis:用于缓存频繁访问的数据(如球员信息)、存储房间状态
- 内存存储:用于选人过程中的临时数据存储,配合定时持久化到 Redis
考虑到项目不需要长期存储历史数据,可以优先使用内存存储,Redis 作为持久化和缓存层。
推荐:Redis + 内存存储
关键技术难点
并发控制与状态同步
问题背景
Ban/Pick 系统的核心挑战在于处理两个用户的同时操作。在网络延迟、用户快速操作的情况下,可能出现以下问题:
- 操作冲突:双方同时选择同一个球员
- 状态不一致:网络延迟导致一方看不到另一方的操作
- 超时处理:服务器端倒计时和客户端倒计时的同步问题
解决方案
乐观锁 + 版本控制:
- 每个房间维护一个版本号(version)
- 每个选人操作都携带当前版本号
- 服务端验证版本号,如果版本不一致则拒绝操作
- 客户端在收到版本冲突时,重新获取最新状态并更新界面
事件溯源模式:
- 所有选人操作作为事件存储
- 按时间顺序回放事件来重建状态
- 保证状态重建的一致性
具体实现:
// 房间状态结构
interface RoomState {
version: number;
currentPlayer: 'home' | 'away';
phase: 'ban' | 'pick' | 'confirm';
homePlayers: Player[];
awayPlayers: Player[];
bannedPlayers: Player[];
lastActionTime: number;
}
// 操作消息
interface PickMessage {
playerId: string;
roomVersion: number; // 操作时的版本号
timestamp: number;
}
// 服务端验证
async function handlePick(message: PickMessage) {
const room = getRoom(message.roomId);
if (room.version !== message.roomVersion) {
return { status: 'conflict', currentVersion: room.version };
}
// 验证操作合法性
if (room.currentPlayer !== 'home') {
return { status: 'invalid', reason: 'not_your_turn' };
}
// 执行操作
const player = room.availablePlayers.find(p => p.id === message.playerId);
if (!player) {
return { status: 'invalid', reason: 'player_not_available' };
}
// 更新状态
room.homePlayers.push(player);
room.availablePlayers = room.availablePlayers.filter(p => p.id !== message.playerId);
room.version++;
// 广播更新
broadcastRoomUpdate(room);
}
倒计时同步
服务器主导的倒计时:
- 倒计时由服务器控制,客户端只显示
- 服务器定期广播剩余时间
- 客户端收到时间更新后更新本地显示
- 网络延迟造成的误差可以通过时间校准解决
心跳检测:
- 客户端定期向服务器发送心跳包
- 服务器检测到用户长时间无响应时,可以主动结束其回合
- 处理网络异常情况下的状态恢复
网络异常处理
问题背景
移动网络环境下,用户可能遇到:
- 网络切换(WiFi 到 4G)
- 信号波动
- 应用被系统挂起
- 网络完全断开
解决方案
智能重连机制:
- 断网检测:客户端定期检测连接状态
- 渐进式重连:1s、2s、4s、8s 的指数退避算法
- 连接恢复后:同步最新状态,补发丢失的操作
- 超时处理:长时间无响应,可提醒用户重新连接
状态缓存:
- 客户端缓存关键状态(当前房间、选人进度)
- 重连后从服务器获取最新状态,比对并同步本地状态
- 对于已完成的部分操作,避免重复执行
离线标记:
- 当检测到用户离线时,界面显示离线状态
- 允许其他用户继续操作
- 用户重新上线后,状态自动同步
性能优化
图片加载优化
懒加载 + 预加载:
- 球员图片使用懒加载,只加载当前视图的球员
- 对于即将显示的球员(如下一批),进行预加载
- 图片格式使用 WebP 或 AVIF,减少文件大小
- 实现图片缓存策略,避免重复下载
渐进式加载:
- 先显示低分辨率图片占位
- 再加载高分辨率完整图片
- 使用模糊效果过渡,提升用户体验
WebSocket 性能优化
消息压缩:
- 使用 JSON 压缩算法减少消息大小
- 对于频繁更新的状态(如倒计时),采用增量更新
- 合并多个状态更新为单次消息
连接管理:
- 单个 WebSocket 连接处理所有消息
- 使用房间频道隔离不同房间的消息
- 实现连接池管理,避免创建过多连接
动画性能优化
GPU 加速:
- 使用
transform和opacity属性触发 GPU 加速 - 避免频繁修改
width、height、left、top属性 - 使用
will-change提示浏览器优化渲染
减少重排重绘:
- 使用
requestAnimationFrame协调动画帧 - 复用 DOM 元素,避免频繁创建和销毁
- 使用 Canvas 绘制复杂动画,如能力值雷达图
架构设计
整体架构
采用微服务架构,但由于项目规模较小,建议采用模块化单体应用架构:
┌─────────────────┐ ┌─────────────────┐
│ H5 Client │ │ Admin Client │
│ (Vue 3) │ │ (Vue 3) │
└────────┬───────┘ └────────┬─────────┘
│ │
▼ ▼
┌─────────────────────────────────────┐
│ WebSocket Server │
│ (NestJS + Socket.IO) │
│ │
│ ┌─────────────┐ ┌──────────────┐ │
│ │ Room Service │ │ Game Logic │ │
│ └─────────────┘ └──────────────┘ │
│ │
│ ┌─────────────┐ ┌──────────────┐ │
│ │ Redis │ │ Data │ │
│ │ Cache │ │ Store │ │
│ └─────────────┘ └──────────────┘ │
└─────────────────────────────────────┘
数据流设计
选人操作的数据流:
- 用户点击球员卡片
- 前端验证操作合法性(当前回合、球员可选)
- 发送 WebSocket 消息到服务器
- 服务器验证操作(版本检查、业务规则)
- 更新房间状态,广播更新消息
- 其他客户端收到更新,刷新界面
- 操作完成后,进入下一阶段或下一回合
状态同步的数据流:
- 服务器维护房间状态
- 定期广播状态更新(包含版本号)
- 客户端收到更新,比对版本号
- 版本不一致时,同步最新状态
- 客户端本地状态与服务器保持一致
部署方案
开发环境
本地开发:
- 使用 Docker Compose 快速搭建开发环境
- 前端使用 Vite 开发服务器,支持热更新
- 后端使用 NestJS 开发服务器,支持自动重启
- Redis 使用 Docker 容器
代码组织:
banpick-game/
├── frontend/ # Vue 3 前端项目
│ ├── src/
│ │ ├── components/ # 组件
│ │ ├── composables/ # 组合式函数
│ │ ├── stores/ # Pinia 状态管理
│ │ └── utils/ # 工具函数
├── backend/ # NestJS 后端项目
│ ├── src/
│ │ ├── modules/
│ │ │ ├── room/ # 房间模块
│ │ │ ├── game/ # 游戏逻辑模块
│ │ │ └── user/ # 用户模块
│ │ └── websockets/ # WebSocket 处理
├── data/ # 静态数据(球员信息等)
└── docker-compose.yml # 开发环境配置
生产环境
云原生部署:
- 使用 Docker 容器化部署
- Kubernetes 进行容器编排和自动扩缩容
- 使用 Nginx 作为反向代理,处理静态资源
- Redis 集群提供高可用缓存
CDN 加速:
- 球员图片和静态资源使用 CDN 加速
- WebSocket 连接直连应用服务器,避免代理延迟
监控日志:
- 使用 Prometheus + Grafana 监控系统状态
- ELK Stack 收集和分析日志
- 前端错误上报机制,及时发现和修复问题
安全考虑
数据安全
输入验证:
- 所有 WebSocket 消息都进行严格验证
- 防止 SQL 注入、XSS 攻击
- 验证操作的权限和合法性
敏感信息保护:
- 球员图片等资源使用临时链接,防止盗用
- 敏感操作需要二次确认
- 防止 CSRF 攻击
通信安全
HTTPS + WSS:
- 使用 HTTPS 加密 HTTP 请求
- 使用 WSS (WebSocket Secure) 加密 WebSocket 连接
- 实现证书管理和自动更新
参考资料
- NestJS 官方文档 - 后端框架参考
- Vue 3 官方文档 - 前端框架参考
- Socket.IO 官方文档 - WebSocket 通信参考
- GSAP 官方文档 - 动画库参考