feat:实现位置广播系统

- 添加位置广播核心控制器和服务
- 实现健康检查和位置同步功能
- 添加WebSocket实时位置更新支持
- 完善位置广播的测试覆盖
This commit is contained in:
moyin
2026-01-08 23:05:52 +08:00
parent 6924416bbd
commit c31cbe559d
27 changed files with 12212 additions and 0 deletions

View File

@@ -0,0 +1,334 @@
/**
* WebSocket消息数据传输对象
*
* 功能描述:
* - 定义WebSocket通信的消息格式和验证规则
* - 提供客户端和服务端之间的数据交换标准
* - 支持位置广播系统的实时通信需求
* - 实现消息类型的统一管理和验证
*
* 职责分离:
* - 消息格式定义WebSocket消息的标准结构
* - 数据验证使用class-validator进行输入验证
* - 类型安全提供TypeScript类型约束
* - 接口规范:统一的消息交换格式
*
* 最近修改:
* - 2026-01-08: 功能新增 - 创建WebSocket消息DTO支持位置广播系统
*
* @author moyin
* @version 1.0.0
* @since 2026-01-08
* @lastModified 2026-01-08
*/
import { IsString, IsNumber, IsNotEmpty, IsOptional, IsObject, Length } from 'class-validator';
import { Type } from 'class-transformer';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
/**
* 加入会话消息DTO
*
* 职责:
* - 定义用户加入游戏会话的请求数据
* - 验证会话ID和认证token的格式
* - 支持可选的初始位置设置
*/
export class JoinSessionMessage {
/**
* 消息类型标识
*/
@ApiProperty({
description: '消息类型',
example: 'join_session',
enum: ['join_session']
})
@IsString({ message: '消息类型必须是字符串' })
@IsOptional()
type?: 'join_session' = 'join_session';
/**
* 游戏会话ID
*/
@ApiProperty({
description: '游戏会话ID',
example: 'session_12345',
minLength: 1,
maxLength: 100
})
@IsString({ message: '会话ID必须是字符串' })
@IsNotEmpty({ message: '会话ID不能为空' })
@Length(1, 100, { message: '会话ID长度必须在1-100个字符之间' })
sessionId: string;
/**
* JWT认证token
*/
@ApiProperty({
description: 'JWT认证token',
example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'
})
@IsString({ message: 'Token必须是字符串' })
@IsNotEmpty({ message: 'Token不能为空' })
token: string;
/**
* 会话密码(可选)
*/
@ApiPropertyOptional({
description: '会话密码(如果会话需要密码)',
example: 'password123'
})
@IsOptional()
@IsString({ message: '会话密码必须是字符串' })
password?: string;
/**
* 初始位置(可选)
*/
@ApiPropertyOptional({
description: '用户初始位置',
example: {
mapId: 'plaza',
x: 100,
y: 200
}
})
@IsOptional()
@IsObject({ message: '初始位置必须是对象格式' })
initialPosition?: {
mapId: string;
x: number;
y: number;
};
}
/**
* 离开会话消息DTO
*
* 职责:
* - 定义用户离开游戏会话的请求数据
* - 支持主动离开和被动断开的区分
* - 提供离开原因的记录
*/
export class LeaveSessionMessage {
/**
* 消息类型标识
*/
@ApiProperty({
description: '消息类型',
example: 'leave_session',
enum: ['leave_session']
})
@IsString({ message: '消息类型必须是字符串' })
@IsOptional()
type?: 'leave_session' = 'leave_session';
/**
* 游戏会话ID
*/
@ApiProperty({
description: '游戏会话ID',
example: 'session_12345'
})
@IsString({ message: '会话ID必须是字符串' })
@IsNotEmpty({ message: '会话ID不能为空' })
sessionId: string;
/**
* 离开原因(可选)
*/
@ApiPropertyOptional({
description: '离开原因',
example: 'user_left',
enum: ['user_left', 'connection_lost', 'kicked', 'error']
})
@IsOptional()
@IsString({ message: '离开原因必须是字符串' })
reason?: string;
}
/**
* 位置更新消息DTO
*
* 职责:
* - 定义用户位置更新的请求数据
* - 验证位置坐标和地图ID的有效性
* - 支持位置元数据的扩展
*/
export class PositionUpdateMessage {
/**
* 消息类型标识
*/
@ApiProperty({
description: '消息类型',
example: 'position_update',
enum: ['position_update']
})
@IsString({ message: '消息类型必须是字符串' })
@IsOptional()
type?: 'position_update' = 'position_update';
/**
* 地图ID
*/
@ApiProperty({
description: '地图ID',
example: 'plaza',
minLength: 1,
maxLength: 50
})
@IsString({ message: '地图ID必须是字符串' })
@IsNotEmpty({ message: '地图ID不能为空' })
@Length(1, 50, { message: '地图ID长度必须在1-50个字符之间' })
mapId: string;
/**
* X轴坐标
*/
@ApiProperty({
description: 'X轴坐标',
example: 100.5,
type: 'number'
})
@IsNumber({}, { message: 'X坐标必须是数字' })
@Type(() => Number)
x: number;
/**
* Y轴坐标
*/
@ApiProperty({
description: 'Y轴坐标',
example: 200.3,
type: 'number'
})
@IsNumber({}, { message: 'Y坐标必须是数字' })
@Type(() => Number)
y: number;
/**
* 时间戳(可选,服务端会自动设置)
*/
@ApiPropertyOptional({
description: '位置更新时间戳',
example: 1641024000000
})
@IsOptional()
@IsNumber({}, { message: '时间戳必须是数字' })
@Type(() => Number)
timestamp?: number;
/**
* 扩展元数据(可选)
*/
@ApiPropertyOptional({
description: '位置扩展元数据',
example: {
speed: 5.2,
direction: 'north'
}
})
@IsOptional()
@IsObject({ message: '元数据必须是对象格式' })
metadata?: Record<string, any>;
}
/**
* 心跳消息DTO
*
* 职责:
* - 定义WebSocket连接的心跳检测消息
* - 维持连接活跃状态
* - 检测连接质量和延迟
*/
export class HeartbeatMessage {
/**
* 消息类型标识
*/
@ApiProperty({
description: '消息类型',
example: 'heartbeat',
enum: ['heartbeat']
})
@IsString({ message: '消息类型必须是字符串' })
@IsOptional()
type?: 'heartbeat' = 'heartbeat';
/**
* 客户端时间戳
*/
@ApiProperty({
description: '客户端发送时间戳',
example: 1641024000000
})
@IsNumber({}, { message: '时间戳必须是数字' })
@Type(() => Number)
timestamp: number;
/**
* 序列号(可选)
*/
@ApiPropertyOptional({
description: '心跳序列号',
example: 1
})
@IsOptional()
@IsNumber({}, { message: '序列号必须是数字' })
@Type(() => Number)
sequence?: number;
}
/**
* 通用WebSocket消息DTO
*
* 职责:
* - 定义所有WebSocket消息的基础结构
* - 提供消息类型的统一管理
* - 支持消息的路由和处理
*/
export class WebSocketMessage {
/**
* 消息类型
*/
@ApiProperty({
description: '消息类型',
example: 'join_session',
enum: ['join_session', 'leave_session', 'position_update', 'heartbeat']
})
@IsString({ message: '消息类型必须是字符串' })
@IsNotEmpty({ message: '消息类型不能为空' })
type: string;
/**
* 消息数据
*/
@ApiProperty({
description: '消息数据',
example: {}
})
@IsObject({ message: '消息数据必须是对象格式' })
data: any;
/**
* 消息ID可选
*/
@ApiPropertyOptional({
description: '消息唯一标识',
example: 'msg_12345'
})
@IsOptional()
@IsString({ message: '消息ID必须是字符串' })
messageId?: string;
/**
* 时间戳
*/
@ApiProperty({
description: '消息时间戳',
example: 1641024000000
})
@IsNumber({}, { message: '时间戳必须是数字' })
@Type(() => Number)
timestamp: number;
}