334 lines
7.4 KiB
TypeScript
334 lines
7.4 KiB
TypeScript
/**
|
||
* 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;
|
||
} |