传输层实现(客户端/服务端,链接管理和心跳机制,重连机制)
消息序列化(json序列化,消息压缩,消息ID和时间戳) 网络服务器核心(networkserver/基础room/链接状态同步) 网络客户端核心(networkclient/消息队列)
This commit is contained in:
550
packages/network-shared/src/serialization/JSONSerializer.ts
Normal file
550
packages/network-shared/src/serialization/JSONSerializer.ts
Normal file
@@ -0,0 +1,550 @@
|
||||
/**
|
||||
* JSON序列化器
|
||||
* 提供高性能的消息序列化和反序列化功能,包括类型安全检查
|
||||
*/
|
||||
import { createLogger } from '@esengine/ecs-framework';
|
||||
import { INetworkMessage, MessageType } from '../types/NetworkTypes';
|
||||
|
||||
/**
|
||||
* 序列化器配置
|
||||
*/
|
||||
export interface SerializerConfig {
|
||||
enableTypeChecking: boolean;
|
||||
enableCompression: boolean;
|
||||
maxMessageSize: number;
|
||||
enableProfiling: boolean;
|
||||
customSerializers?: Map<string, ICustomSerializer>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义序列化器接口
|
||||
*/
|
||||
export interface ICustomSerializer {
|
||||
serialize(data: any): any;
|
||||
deserialize(data: any): any;
|
||||
canHandle(data: any): boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化结果
|
||||
*/
|
||||
export interface SerializationResult {
|
||||
data: string | Buffer;
|
||||
size: number;
|
||||
compressionRatio?: number;
|
||||
serializationTime: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 反序列化结果
|
||||
*/
|
||||
export interface DeserializationResult<T = any> {
|
||||
data: T;
|
||||
deserializationTime: number;
|
||||
isValid: boolean;
|
||||
errors?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化统计信息
|
||||
*/
|
||||
export interface SerializationStats {
|
||||
totalSerialized: number;
|
||||
totalDeserialized: number;
|
||||
totalBytes: number;
|
||||
averageSerializationTime: number;
|
||||
averageDeserializationTime: number;
|
||||
averageMessageSize: number;
|
||||
errorCount: number;
|
||||
compressionSavings: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON序列化器
|
||||
*/
|
||||
export class JSONSerializer {
|
||||
private logger = createLogger('JSONSerializer');
|
||||
private config: SerializerConfig;
|
||||
private stats: SerializationStats;
|
||||
|
||||
// 性能分析
|
||||
private serializationTimes: number[] = [];
|
||||
private deserializationTimes: number[] = [];
|
||||
private messageSizes: number[] = [];
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*/
|
||||
constructor(config: Partial<SerializerConfig> = {}) {
|
||||
this.config = {
|
||||
enableTypeChecking: true,
|
||||
enableCompression: false,
|
||||
maxMessageSize: 1024 * 1024, // 1MB
|
||||
enableProfiling: false,
|
||||
...config
|
||||
};
|
||||
|
||||
this.stats = {
|
||||
totalSerialized: 0,
|
||||
totalDeserialized: 0,
|
||||
totalBytes: 0,
|
||||
averageSerializationTime: 0,
|
||||
averageDeserializationTime: 0,
|
||||
averageMessageSize: 0,
|
||||
errorCount: 0,
|
||||
compressionSavings: 0
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化消息
|
||||
*/
|
||||
serialize<T extends INetworkMessage>(message: T): SerializationResult {
|
||||
const startTime = performance.now();
|
||||
|
||||
try {
|
||||
// 类型检查
|
||||
if (this.config.enableTypeChecking) {
|
||||
this.validateMessage(message);
|
||||
}
|
||||
|
||||
// 预处理消息
|
||||
const processedMessage = this.preprocessMessage(message);
|
||||
|
||||
// 序列化
|
||||
let serializedData: string;
|
||||
|
||||
// 使用自定义序列化器
|
||||
const customSerializer = this.findCustomSerializer(processedMessage);
|
||||
if (customSerializer) {
|
||||
serializedData = JSON.stringify(customSerializer.serialize(processedMessage));
|
||||
} else {
|
||||
serializedData = JSON.stringify(processedMessage, this.createReplacer());
|
||||
}
|
||||
|
||||
// 检查大小限制
|
||||
if (serializedData.length > this.config.maxMessageSize) {
|
||||
throw new Error(`消息大小超过限制: ${serializedData.length} > ${this.config.maxMessageSize}`);
|
||||
}
|
||||
|
||||
const endTime = performance.now();
|
||||
const serializationTime = endTime - startTime;
|
||||
|
||||
// 更新统计
|
||||
this.updateSerializationStats(serializedData.length, serializationTime);
|
||||
|
||||
return {
|
||||
data: serializedData,
|
||||
size: serializedData.length,
|
||||
serializationTime
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
this.stats.errorCount++;
|
||||
this.logger.error('序列化失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 反序列化消息
|
||||
*/
|
||||
deserialize<T extends INetworkMessage>(data: string | ArrayBuffer): DeserializationResult<T> {
|
||||
const startTime = performance.now();
|
||||
|
||||
try {
|
||||
// 转换数据格式
|
||||
const jsonString = data instanceof ArrayBuffer ? new TextDecoder().decode(data) :
|
||||
typeof data === 'string' ? data : String(data);
|
||||
|
||||
// 解析JSON
|
||||
const parsedData = JSON.parse(jsonString, this.createReviver());
|
||||
|
||||
// 类型检查
|
||||
const validationResult = this.config.enableTypeChecking ?
|
||||
this.validateParsedMessage(parsedData) : { isValid: true, errors: [] };
|
||||
|
||||
// 后处理消息
|
||||
const processedMessage = this.postprocessMessage(parsedData);
|
||||
|
||||
const endTime = performance.now();
|
||||
const deserializationTime = endTime - startTime;
|
||||
|
||||
// 更新统计
|
||||
this.updateDeserializationStats(deserializationTime);
|
||||
|
||||
return {
|
||||
data: processedMessage as T,
|
||||
deserializationTime,
|
||||
isValid: validationResult.isValid,
|
||||
errors: validationResult.errors
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
this.stats.errorCount++;
|
||||
this.logger.error('反序列化失败:', error);
|
||||
|
||||
return {
|
||||
data: {} as T,
|
||||
deserializationTime: performance.now() - startTime,
|
||||
isValid: false,
|
||||
errors: [error instanceof Error ? error.message : '未知错误']
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量序列化
|
||||
*/
|
||||
serializeBatch<T extends INetworkMessage>(messages: T[]): SerializationResult {
|
||||
const startTime = performance.now();
|
||||
|
||||
try {
|
||||
const batchData = {
|
||||
type: 'batch',
|
||||
messages: messages.map(msg => {
|
||||
if (this.config.enableTypeChecking) {
|
||||
this.validateMessage(msg);
|
||||
}
|
||||
return this.preprocessMessage(msg);
|
||||
}),
|
||||
timestamp: Date.now()
|
||||
};
|
||||
|
||||
const serializedData = JSON.stringify(batchData, this.createReplacer());
|
||||
|
||||
if (serializedData.length > this.config.maxMessageSize) {
|
||||
throw new Error(`批量消息大小超过限制: ${serializedData.length} > ${this.config.maxMessageSize}`);
|
||||
}
|
||||
|
||||
const endTime = performance.now();
|
||||
const serializationTime = endTime - startTime;
|
||||
|
||||
this.updateSerializationStats(serializedData.length, serializationTime);
|
||||
|
||||
return {
|
||||
data: serializedData,
|
||||
size: serializedData.length,
|
||||
serializationTime
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
this.stats.errorCount++;
|
||||
this.logger.error('批量序列化失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量反序列化
|
||||
*/
|
||||
deserializeBatch<T extends INetworkMessage>(data: string | ArrayBuffer): DeserializationResult<T[]> {
|
||||
const result = this.deserialize<any>(data);
|
||||
|
||||
if (!result.isValid || !result.data.messages) {
|
||||
return {
|
||||
data: [],
|
||||
deserializationTime: result.deserializationTime,
|
||||
isValid: false,
|
||||
errors: ['无效的批量消息格式']
|
||||
};
|
||||
}
|
||||
|
||||
const messages = result.data.messages.map((msg: any) => this.postprocessMessage(msg));
|
||||
|
||||
return {
|
||||
data: messages as T[],
|
||||
deserializationTime: result.deserializationTime,
|
||||
isValid: true
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取统计信息
|
||||
*/
|
||||
getStats(): SerializationStats {
|
||||
return { ...this.stats };
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置统计信息
|
||||
*/
|
||||
resetStats(): void {
|
||||
this.stats = {
|
||||
totalSerialized: 0,
|
||||
totalDeserialized: 0,
|
||||
totalBytes: 0,
|
||||
averageSerializationTime: 0,
|
||||
averageDeserializationTime: 0,
|
||||
averageMessageSize: 0,
|
||||
errorCount: 0,
|
||||
compressionSavings: 0
|
||||
};
|
||||
|
||||
this.serializationTimes.length = 0;
|
||||
this.deserializationTimes.length = 0;
|
||||
this.messageSizes.length = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加自定义序列化器
|
||||
*/
|
||||
addCustomSerializer(name: string, serializer: ICustomSerializer): void {
|
||||
if (!this.config.customSerializers) {
|
||||
this.config.customSerializers = new Map();
|
||||
}
|
||||
this.config.customSerializers.set(name, serializer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除自定义序列化器
|
||||
*/
|
||||
removeCustomSerializer(name: string): boolean {
|
||||
return this.config.customSerializers?.delete(name) || false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新配置
|
||||
*/
|
||||
updateConfig(newConfig: Partial<SerializerConfig>): void {
|
||||
Object.assign(this.config, newConfig);
|
||||
this.logger.info('序列化器配置已更新:', newConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证消息格式
|
||||
*/
|
||||
private validateMessage(message: INetworkMessage): void {
|
||||
if (!message.type || !message.messageId || !message.timestamp) {
|
||||
throw new Error('消息格式无效:缺少必需字段');
|
||||
}
|
||||
|
||||
if (!Object.values(MessageType).includes(message.type)) {
|
||||
throw new Error(`无效的消息类型: ${message.type}`);
|
||||
}
|
||||
|
||||
if (typeof message.timestamp !== 'number' || message.timestamp <= 0) {
|
||||
throw new Error('无效的时间戳');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证解析后的消息
|
||||
*/
|
||||
private validateParsedMessage(data: any): { isValid: boolean; errors: string[] } {
|
||||
const errors: string[] = [];
|
||||
|
||||
if (!data || typeof data !== 'object') {
|
||||
errors.push('消息必须是对象');
|
||||
} else {
|
||||
if (!data.type) errors.push('缺少消息类型');
|
||||
if (!data.messageId) errors.push('缺少消息ID');
|
||||
if (!data.timestamp) errors.push('缺少时间戳');
|
||||
if (!data.senderId) errors.push('缺少发送者ID');
|
||||
}
|
||||
|
||||
return {
|
||||
isValid: errors.length === 0,
|
||||
errors
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 预处理消息(序列化前)
|
||||
*/
|
||||
private preprocessMessage(message: INetworkMessage): any {
|
||||
// 克隆消息以避免修改原始对象
|
||||
const processed = { ...message };
|
||||
|
||||
// 处理特殊数据类型
|
||||
if (processed.data) {
|
||||
processed.data = this.serializeSpecialTypes(processed.data);
|
||||
}
|
||||
|
||||
return processed;
|
||||
}
|
||||
|
||||
/**
|
||||
* 后处理消息(反序列化后)
|
||||
*/
|
||||
private postprocessMessage(data: any): any {
|
||||
if (data.data) {
|
||||
data.data = this.deserializeSpecialTypes(data.data);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化特殊类型
|
||||
*/
|
||||
private serializeSpecialTypes(data: any): any {
|
||||
if (data instanceof Date) {
|
||||
return { __type: 'Date', value: data.toISOString() };
|
||||
} else if (data instanceof Map) {
|
||||
return { __type: 'Map', value: Array.from(data.entries()) };
|
||||
} else if (data instanceof Set) {
|
||||
return { __type: 'Set', value: Array.from(data) };
|
||||
} else if (ArrayBuffer.isView(data)) {
|
||||
return { __type: 'TypedArray', value: Array.from(data as any), constructor: data.constructor.name };
|
||||
} else if (data && typeof data === 'object') {
|
||||
const result: any = {};
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
result[key] = this.serializeSpecialTypes(value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 反序列化特殊类型
|
||||
*/
|
||||
private deserializeSpecialTypes(data: any): any {
|
||||
if (data && typeof data === 'object' && data.__type) {
|
||||
switch (data.__type) {
|
||||
case 'Date':
|
||||
return new Date(data.value);
|
||||
case 'Map':
|
||||
return new Map(data.value);
|
||||
case 'Set':
|
||||
return new Set(data.value);
|
||||
case 'TypedArray':
|
||||
const constructor = (globalThis as any)[data.constructor];
|
||||
return constructor ? new constructor(data.value) : data.value;
|
||||
}
|
||||
} else if (data && typeof data === 'object') {
|
||||
const result: any = {};
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
result[key] = this.deserializeSpecialTypes(value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建JSON.stringify替换函数
|
||||
*/
|
||||
private createReplacer() {
|
||||
return (key: string, value: any) => {
|
||||
// 处理循环引用
|
||||
if (value && typeof value === 'object') {
|
||||
if (value.__serializing) {
|
||||
return '[Circular Reference]';
|
||||
}
|
||||
value.__serializing = true;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建JSON.parse恢复函数
|
||||
*/
|
||||
private createReviver() {
|
||||
return (key: string, value: any) => {
|
||||
// 清理序列化标记
|
||||
if (value && typeof value === 'object') {
|
||||
delete value.__serializing;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找自定义序列化器
|
||||
*/
|
||||
private findCustomSerializer(data: any): ICustomSerializer | undefined {
|
||||
if (!this.config.customSerializers) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
for (const serializer of this.config.customSerializers.values()) {
|
||||
if (serializer.canHandle(data)) {
|
||||
return serializer;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新序列化统计
|
||||
*/
|
||||
private updateSerializationStats(size: number, time: number): void {
|
||||
this.stats.totalSerialized++;
|
||||
this.stats.totalBytes += size;
|
||||
|
||||
this.serializationTimes.push(time);
|
||||
this.messageSizes.push(size);
|
||||
|
||||
// 保持最近1000个样本
|
||||
if (this.serializationTimes.length > 1000) {
|
||||
this.serializationTimes.shift();
|
||||
}
|
||||
if (this.messageSizes.length > 1000) {
|
||||
this.messageSizes.shift();
|
||||
}
|
||||
|
||||
// 计算平均值
|
||||
this.stats.averageSerializationTime =
|
||||
this.serializationTimes.reduce((sum, t) => sum + t, 0) / this.serializationTimes.length;
|
||||
this.stats.averageMessageSize =
|
||||
this.messageSizes.reduce((sum, s) => sum + s, 0) / this.messageSizes.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新反序列化统计
|
||||
*/
|
||||
private updateDeserializationStats(time: number): void {
|
||||
this.stats.totalDeserialized++;
|
||||
|
||||
this.deserializationTimes.push(time);
|
||||
|
||||
// 保持最近1000个样本
|
||||
if (this.deserializationTimes.length > 1000) {
|
||||
this.deserializationTimes.shift();
|
||||
}
|
||||
|
||||
// 计算平均值
|
||||
this.stats.averageDeserializationTime =
|
||||
this.deserializationTimes.reduce((sum, t) => sum + t, 0) / this.deserializationTimes.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取性能分析报告
|
||||
*/
|
||||
getPerformanceReport() {
|
||||
return {
|
||||
stats: this.getStats(),
|
||||
serializationTimes: [...this.serializationTimes],
|
||||
deserializationTimes: [...this.deserializationTimes],
|
||||
messageSizes: [...this.messageSizes],
|
||||
percentiles: {
|
||||
serialization: this.calculatePercentiles(this.serializationTimes),
|
||||
deserialization: this.calculatePercentiles(this.deserializationTimes),
|
||||
messageSize: this.calculatePercentiles(this.messageSizes)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算百分位数
|
||||
*/
|
||||
private calculatePercentiles(values: number[]) {
|
||||
if (values.length === 0) return {};
|
||||
|
||||
const sorted = [...values].sort((a, b) => a - b);
|
||||
const n = sorted.length;
|
||||
|
||||
return {
|
||||
p50: sorted[Math.floor(n * 0.5)],
|
||||
p90: sorted[Math.floor(n * 0.9)],
|
||||
p95: sorted[Math.floor(n * 0.95)],
|
||||
p99: sorted[Math.floor(n * 0.99)]
|
||||
};
|
||||
}
|
||||
}
|
||||
498
packages/network-shared/src/serialization/MessageCompressor.ts
Normal file
498
packages/network-shared/src/serialization/MessageCompressor.ts
Normal file
@@ -0,0 +1,498 @@
|
||||
/**
|
||||
* 消息压缩器
|
||||
* 提供多种压缩算法选择和压缩率统计
|
||||
*/
|
||||
import { createLogger } from '@esengine/ecs-framework';
|
||||
import * as zlib from 'zlib';
|
||||
import { promisify } from 'util';
|
||||
|
||||
/**
|
||||
* 压缩算法类型
|
||||
*/
|
||||
export enum CompressionAlgorithm {
|
||||
NONE = 'none',
|
||||
GZIP = 'gzip',
|
||||
DEFLATE = 'deflate',
|
||||
BROTLI = 'brotli'
|
||||
}
|
||||
|
||||
/**
|
||||
* 压缩配置
|
||||
*/
|
||||
export interface CompressionConfig {
|
||||
algorithm: CompressionAlgorithm;
|
||||
level: number; // 压缩级别 (0-9)
|
||||
threshold: number; // 最小压缩阈值(字节)
|
||||
enableAsync: boolean; // 是否启用异步压缩
|
||||
chunkSize: number; // 分块大小
|
||||
}
|
||||
|
||||
/**
|
||||
* 压缩结果
|
||||
*/
|
||||
export interface CompressionResult {
|
||||
data: Buffer;
|
||||
originalSize: number;
|
||||
compressedSize: number;
|
||||
compressionRatio: number;
|
||||
compressionTime: number;
|
||||
algorithm: CompressionAlgorithm;
|
||||
}
|
||||
|
||||
/**
|
||||
* 压缩统计信息
|
||||
*/
|
||||
export interface CompressionStats {
|
||||
totalCompressed: number;
|
||||
totalDecompressed: number;
|
||||
totalOriginalBytes: number;
|
||||
totalCompressedBytes: number;
|
||||
averageCompressionRatio: number;
|
||||
averageCompressionTime: number;
|
||||
averageDecompressionTime: number;
|
||||
algorithmUsage: Record<CompressionAlgorithm, number>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息压缩器
|
||||
*/
|
||||
export class MessageCompressor {
|
||||
private logger = createLogger('MessageCompressor');
|
||||
private config: CompressionConfig;
|
||||
private stats: CompressionStats;
|
||||
|
||||
// 异步压缩函数
|
||||
private gzipAsync = promisify(zlib.gzip);
|
||||
private gunzipAsync = promisify(zlib.gunzip);
|
||||
private deflateAsync = promisify(zlib.deflate);
|
||||
private inflateAsync = promisify(zlib.inflate);
|
||||
private brotliCompressAsync = promisify(zlib.brotliCompress);
|
||||
private brotliDecompressAsync = promisify(zlib.brotliDecompress);
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*/
|
||||
constructor(config: Partial<CompressionConfig> = {}) {
|
||||
this.config = {
|
||||
algorithm: CompressionAlgorithm.GZIP,
|
||||
level: 6, // 平衡压缩率和速度
|
||||
threshold: 1024, // 1KB以上才压缩
|
||||
enableAsync: true,
|
||||
chunkSize: 64 * 1024, // 64KB分块
|
||||
...config
|
||||
};
|
||||
|
||||
this.stats = {
|
||||
totalCompressed: 0,
|
||||
totalDecompressed: 0,
|
||||
totalOriginalBytes: 0,
|
||||
totalCompressedBytes: 0,
|
||||
averageCompressionRatio: 0,
|
||||
averageCompressionTime: 0,
|
||||
averageDecompressionTime: 0,
|
||||
algorithmUsage: {
|
||||
[CompressionAlgorithm.NONE]: 0,
|
||||
[CompressionAlgorithm.GZIP]: 0,
|
||||
[CompressionAlgorithm.DEFLATE]: 0,
|
||||
[CompressionAlgorithm.BROTLI]: 0
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 压缩数据
|
||||
*/
|
||||
async compress(data: string | Buffer): Promise<CompressionResult> {
|
||||
const startTime = performance.now();
|
||||
const inputBuffer = typeof data === 'string' ? Buffer.from(data, 'utf8') : data;
|
||||
const originalSize = inputBuffer.length;
|
||||
|
||||
try {
|
||||
// 检查是否需要压缩
|
||||
if (originalSize < this.config.threshold) {
|
||||
return this.createNoCompressionResult(inputBuffer, originalSize, startTime);
|
||||
}
|
||||
|
||||
let compressedData: Buffer;
|
||||
const algorithm = this.config.algorithm;
|
||||
|
||||
// 根据算法进行压缩
|
||||
switch (algorithm) {
|
||||
case CompressionAlgorithm.GZIP:
|
||||
compressedData = await this.compressGzip(inputBuffer);
|
||||
break;
|
||||
case CompressionAlgorithm.DEFLATE:
|
||||
compressedData = await this.compressDeflate(inputBuffer);
|
||||
break;
|
||||
case CompressionAlgorithm.BROTLI:
|
||||
compressedData = await this.compressBrotli(inputBuffer);
|
||||
break;
|
||||
case CompressionAlgorithm.NONE:
|
||||
default:
|
||||
return this.createNoCompressionResult(inputBuffer, originalSize, startTime);
|
||||
}
|
||||
|
||||
const endTime = performance.now();
|
||||
const compressionTime = endTime - startTime;
|
||||
const compressedSize = compressedData.length;
|
||||
const compressionRatio = originalSize > 0 ? compressedSize / originalSize : 1;
|
||||
|
||||
// 检查压缩效果
|
||||
if (compressedSize >= originalSize * 0.9) {
|
||||
// 压缩效果不明显,返回原始数据
|
||||
this.logger.debug(`压缩效果不佳,返回原始数据。原始: ${originalSize}, 压缩: ${compressedSize}`);
|
||||
return this.createNoCompressionResult(inputBuffer, originalSize, startTime);
|
||||
}
|
||||
|
||||
const result: CompressionResult = {
|
||||
data: compressedData,
|
||||
originalSize,
|
||||
compressedSize,
|
||||
compressionRatio,
|
||||
compressionTime,
|
||||
algorithm
|
||||
};
|
||||
|
||||
// 更新统计
|
||||
this.updateCompressionStats(result);
|
||||
|
||||
return result;
|
||||
|
||||
} catch (error) {
|
||||
this.logger.error('压缩失败:', error);
|
||||
return this.createNoCompressionResult(inputBuffer, originalSize, startTime);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解压缩数据
|
||||
*/
|
||||
async decompress(data: Buffer, algorithm: CompressionAlgorithm): Promise<Buffer> {
|
||||
const startTime = performance.now();
|
||||
|
||||
try {
|
||||
if (algorithm === CompressionAlgorithm.NONE) {
|
||||
return data;
|
||||
}
|
||||
|
||||
let decompressedData: Buffer;
|
||||
|
||||
switch (algorithm) {
|
||||
case CompressionAlgorithm.GZIP:
|
||||
decompressedData = await this.decompressGzip(data);
|
||||
break;
|
||||
case CompressionAlgorithm.DEFLATE:
|
||||
decompressedData = await this.decompressDeflate(data);
|
||||
break;
|
||||
case CompressionAlgorithm.BROTLI:
|
||||
decompressedData = await this.decompressBrotli(data);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`不支持的压缩算法: ${algorithm}`);
|
||||
}
|
||||
|
||||
const endTime = performance.now();
|
||||
const decompressionTime = endTime - startTime;
|
||||
|
||||
// 更新统计
|
||||
this.updateDecompressionStats(decompressionTime);
|
||||
|
||||
return decompressedData;
|
||||
|
||||
} catch (error) {
|
||||
this.logger.error('解压缩失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量压缩
|
||||
*/
|
||||
async compressBatch(dataList: (string | Buffer)[]): Promise<CompressionResult[]> {
|
||||
const results: CompressionResult[] = [];
|
||||
|
||||
if (this.config.enableAsync) {
|
||||
// 并行压缩
|
||||
const promises = dataList.map(data => this.compress(data));
|
||||
return await Promise.all(promises);
|
||||
} else {
|
||||
// 串行压缩
|
||||
for (const data of dataList) {
|
||||
results.push(await this.compress(data));
|
||||
}
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 自适应压缩
|
||||
* 根据数据特征自动选择最佳压缩算法
|
||||
*/
|
||||
async compressAdaptive(data: string | Buffer): Promise<CompressionResult> {
|
||||
const inputBuffer = typeof data === 'string' ? Buffer.from(data, 'utf8') : data;
|
||||
const originalAlgorithm = this.config.algorithm;
|
||||
|
||||
try {
|
||||
// 对小数据进行算法测试
|
||||
const testSize = Math.min(inputBuffer.length, 4096); // 测试前4KB
|
||||
const testData = inputBuffer.subarray(0, testSize);
|
||||
|
||||
const algorithms = [
|
||||
CompressionAlgorithm.GZIP,
|
||||
CompressionAlgorithm.DEFLATE,
|
||||
CompressionAlgorithm.BROTLI
|
||||
];
|
||||
|
||||
let bestAlgorithm = CompressionAlgorithm.GZIP;
|
||||
let bestRatio = 1;
|
||||
|
||||
// 测试不同算法的压缩效果
|
||||
for (const algorithm of algorithms) {
|
||||
try {
|
||||
this.config.algorithm = algorithm;
|
||||
const testResult = await this.compress(testData);
|
||||
|
||||
if (testResult.compressionRatio < bestRatio) {
|
||||
bestRatio = testResult.compressionRatio;
|
||||
bestAlgorithm = algorithm;
|
||||
}
|
||||
} catch (error) {
|
||||
// 忽略测试失败的算法
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// 使用最佳算法压缩完整数据
|
||||
this.config.algorithm = bestAlgorithm;
|
||||
const result = await this.compress(inputBuffer);
|
||||
|
||||
this.logger.debug(`自适应压缩选择算法: ${bestAlgorithm}, 压缩率: ${result.compressionRatio.toFixed(3)}`);
|
||||
|
||||
return result;
|
||||
|
||||
} finally {
|
||||
// 恢复原始配置
|
||||
this.config.algorithm = originalAlgorithm;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取统计信息
|
||||
*/
|
||||
getStats(): CompressionStats {
|
||||
return { ...this.stats };
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置统计信息
|
||||
*/
|
||||
resetStats(): void {
|
||||
this.stats = {
|
||||
totalCompressed: 0,
|
||||
totalDecompressed: 0,
|
||||
totalOriginalBytes: 0,
|
||||
totalCompressedBytes: 0,
|
||||
averageCompressionRatio: 0,
|
||||
averageCompressionTime: 0,
|
||||
averageDecompressionTime: 0,
|
||||
algorithmUsage: {
|
||||
[CompressionAlgorithm.NONE]: 0,
|
||||
[CompressionAlgorithm.GZIP]: 0,
|
||||
[CompressionAlgorithm.DEFLATE]: 0,
|
||||
[CompressionAlgorithm.BROTLI]: 0
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新配置
|
||||
*/
|
||||
updateConfig(newConfig: Partial<CompressionConfig>): void {
|
||||
Object.assign(this.config, newConfig);
|
||||
this.logger.info('压缩器配置已更新:', newConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取压缩建议
|
||||
*/
|
||||
getCompressionRecommendation(dataSize: number, dataType: string): CompressionAlgorithm {
|
||||
// 根据数据大小和类型推荐压缩算法
|
||||
if (dataSize < this.config.threshold) {
|
||||
return CompressionAlgorithm.NONE;
|
||||
}
|
||||
|
||||
if (dataType === 'json' || dataType === 'text') {
|
||||
// 文本数据推荐GZIP
|
||||
return CompressionAlgorithm.GZIP;
|
||||
} else if (dataType === 'binary') {
|
||||
// 二进制数据推荐DEFLATE
|
||||
return CompressionAlgorithm.DEFLATE;
|
||||
} else {
|
||||
// 默认推荐GZIP
|
||||
return CompressionAlgorithm.GZIP;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GZIP压缩
|
||||
*/
|
||||
private async compressGzip(data: Buffer): Promise<Buffer> {
|
||||
if (this.config.enableAsync) {
|
||||
return await this.gzipAsync(data, { level: this.config.level });
|
||||
} else {
|
||||
return zlib.gzipSync(data, { level: this.config.level });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GZIP解压缩
|
||||
*/
|
||||
private async decompressGzip(data: Buffer): Promise<Buffer> {
|
||||
if (this.config.enableAsync) {
|
||||
return await this.gunzipAsync(data);
|
||||
} else {
|
||||
return zlib.gunzipSync(data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DEFLATE压缩
|
||||
*/
|
||||
private async compressDeflate(data: Buffer): Promise<Buffer> {
|
||||
if (this.config.enableAsync) {
|
||||
return await this.deflateAsync(data, { level: this.config.level });
|
||||
} else {
|
||||
return zlib.deflateSync(data, { level: this.config.level });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DEFLATE解压缩
|
||||
*/
|
||||
private async decompressDeflate(data: Buffer): Promise<Buffer> {
|
||||
if (this.config.enableAsync) {
|
||||
return await this.inflateAsync(data);
|
||||
} else {
|
||||
return zlib.inflateSync(data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* BROTLI压缩
|
||||
*/
|
||||
private async compressBrotli(data: Buffer): Promise<Buffer> {
|
||||
const options = {
|
||||
params: {
|
||||
[zlib.constants.BROTLI_PARAM_QUALITY]: this.config.level
|
||||
}
|
||||
};
|
||||
|
||||
if (this.config.enableAsync) {
|
||||
return await this.brotliCompressAsync(data, options);
|
||||
} else {
|
||||
return zlib.brotliCompressSync(data, options);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* BROTLI解压缩
|
||||
*/
|
||||
private async decompressBrotli(data: Buffer): Promise<Buffer> {
|
||||
if (this.config.enableAsync) {
|
||||
return await this.brotliDecompressAsync(data);
|
||||
} else {
|
||||
return zlib.brotliDecompressSync(data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建无压缩结果
|
||||
*/
|
||||
private createNoCompressionResult(
|
||||
data: Buffer,
|
||||
originalSize: number,
|
||||
startTime: number
|
||||
): CompressionResult {
|
||||
const endTime = performance.now();
|
||||
const result: CompressionResult = {
|
||||
data,
|
||||
originalSize,
|
||||
compressedSize: originalSize,
|
||||
compressionRatio: 1,
|
||||
compressionTime: endTime - startTime,
|
||||
algorithm: CompressionAlgorithm.NONE
|
||||
};
|
||||
|
||||
this.updateCompressionStats(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新压缩统计
|
||||
*/
|
||||
private updateCompressionStats(result: CompressionResult): void {
|
||||
this.stats.totalCompressed++;
|
||||
this.stats.totalOriginalBytes += result.originalSize;
|
||||
this.stats.totalCompressedBytes += result.compressedSize;
|
||||
this.stats.algorithmUsage[result.algorithm]++;
|
||||
|
||||
// 计算平均值
|
||||
this.stats.averageCompressionRatio =
|
||||
this.stats.totalOriginalBytes > 0 ?
|
||||
this.stats.totalCompressedBytes / this.stats.totalOriginalBytes : 1;
|
||||
|
||||
// 更新平均压缩时间(使用移动平均)
|
||||
const alpha = 0.1; // 平滑因子
|
||||
this.stats.averageCompressionTime =
|
||||
this.stats.averageCompressionTime * (1 - alpha) + result.compressionTime * alpha;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新解压缩统计
|
||||
*/
|
||||
private updateDecompressionStats(decompressionTime: number): void {
|
||||
this.stats.totalDecompressed++;
|
||||
|
||||
// 更新平均解压缩时间(使用移动平均)
|
||||
const alpha = 0.1;
|
||||
this.stats.averageDecompressionTime =
|
||||
this.stats.averageDecompressionTime * (1 - alpha) + decompressionTime * alpha;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取压缩效率报告
|
||||
*/
|
||||
getEfficiencyReport() {
|
||||
const savings = this.stats.totalOriginalBytes - this.stats.totalCompressedBytes;
|
||||
const savingsPercentage = this.stats.totalOriginalBytes > 0 ?
|
||||
(savings / this.stats.totalOriginalBytes) * 100 : 0;
|
||||
|
||||
return {
|
||||
totalSavings: savings,
|
||||
savingsPercentage,
|
||||
averageCompressionRatio: this.stats.averageCompressionRatio,
|
||||
averageCompressionTime: this.stats.averageCompressionTime,
|
||||
averageDecompressionTime: this.stats.averageDecompressionTime,
|
||||
algorithmUsage: this.stats.algorithmUsage,
|
||||
recommendation: this.generateRecommendation()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成优化建议
|
||||
*/
|
||||
private generateRecommendation(): string {
|
||||
const ratio = this.stats.averageCompressionRatio;
|
||||
const time = this.stats.averageCompressionTime;
|
||||
|
||||
if (ratio > 0.8) {
|
||||
return '压缩效果较差,建议调整算法或提高压缩级别';
|
||||
} else if (time > 50) {
|
||||
return '压缩时间较长,建议降低压缩级别或使用更快的算法';
|
||||
} else if (ratio < 0.3) {
|
||||
return '压缩效果很好,当前配置最优';
|
||||
} else {
|
||||
return '压缩性能正常';
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user