2025-08-14 23:59:00 +08:00
|
|
|
|
/**
|
2025-08-20 10:16:54 +08:00
|
|
|
|
* 消息压缩器框架
|
|
|
|
|
|
* 提供可扩展的压缩算法接口,用户可以注册自定义压缩算法
|
2025-08-14 23:59:00 +08:00
|
|
|
|
*/
|
|
|
|
|
|
import { createLogger } from '@esengine/ecs-framework';
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-20 10:16:54 +08:00
|
|
|
|
* 压缩算法接口
|
2025-08-14 23:59:00 +08:00
|
|
|
|
*/
|
2025-08-20 10:16:54 +08:00
|
|
|
|
export interface ICompressionAlgorithm {
|
|
|
|
|
|
/** 算法名称 */
|
|
|
|
|
|
readonly name: string;
|
|
|
|
|
|
/** 算法版本 */
|
|
|
|
|
|
readonly version: string;
|
|
|
|
|
|
/** 是否支持异步压缩 */
|
|
|
|
|
|
readonly supportsAsync: boolean;
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 同步压缩
|
|
|
|
|
|
*/
|
|
|
|
|
|
compress(data: ArrayBuffer): ArrayBuffer;
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 同步解压缩
|
|
|
|
|
|
*/
|
|
|
|
|
|
decompress(data: ArrayBuffer): ArrayBuffer;
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 异步压缩(可选)
|
|
|
|
|
|
*/
|
|
|
|
|
|
compressAsync?(data: ArrayBuffer): Promise<ArrayBuffer>;
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 异步解压缩(可选)
|
|
|
|
|
|
*/
|
|
|
|
|
|
decompressAsync?(data: ArrayBuffer): Promise<ArrayBuffer>;
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 估算压缩后大小(可选)
|
|
|
|
|
|
*/
|
|
|
|
|
|
estimateCompressedSize?(data: ArrayBuffer): number;
|
2025-08-14 23:59:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 压缩配置
|
|
|
|
|
|
*/
|
|
|
|
|
|
export interface CompressionConfig {
|
2025-08-20 10:16:54 +08:00
|
|
|
|
/** 默认压缩算法名称 */
|
|
|
|
|
|
defaultAlgorithm: string;
|
|
|
|
|
|
/** 最小压缩阈值(字节) */
|
|
|
|
|
|
threshold: number;
|
|
|
|
|
|
/** 是否启用异步压缩 */
|
|
|
|
|
|
enableAsync: boolean;
|
|
|
|
|
|
/** 压缩级别提示 (0-9) */
|
|
|
|
|
|
level: number;
|
|
|
|
|
|
/** 分块大小 */
|
|
|
|
|
|
chunkSize: number;
|
|
|
|
|
|
/** 是否启用压缩统计 */
|
|
|
|
|
|
enableStats: boolean;
|
2025-08-14 23:59:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 压缩结果
|
|
|
|
|
|
*/
|
|
|
|
|
|
export interface CompressionResult {
|
2025-08-20 10:16:54 +08:00
|
|
|
|
/** 压缩后的数据 */
|
|
|
|
|
|
data: ArrayBuffer;
|
|
|
|
|
|
/** 原始大小 */
|
2025-08-14 23:59:00 +08:00
|
|
|
|
originalSize: number;
|
2025-08-20 10:16:54 +08:00
|
|
|
|
/** 压缩后大小 */
|
2025-08-14 23:59:00 +08:00
|
|
|
|
compressedSize: number;
|
2025-08-20 10:16:54 +08:00
|
|
|
|
/** 压缩比 */
|
2025-08-14 23:59:00 +08:00
|
|
|
|
compressionRatio: number;
|
2025-08-20 10:16:54 +08:00
|
|
|
|
/** 压缩耗时 */
|
2025-08-14 23:59:00 +08:00
|
|
|
|
compressionTime: number;
|
2025-08-20 10:16:54 +08:00
|
|
|
|
/** 使用的算法 */
|
|
|
|
|
|
algorithm: string;
|
|
|
|
|
|
/** 是否实际进行了压缩 */
|
|
|
|
|
|
wasCompressed: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 解压缩结果
|
|
|
|
|
|
*/
|
|
|
|
|
|
export interface DecompressionResult {
|
|
|
|
|
|
/** 解压缩后的数据 */
|
|
|
|
|
|
data: ArrayBuffer;
|
|
|
|
|
|
/** 原始压缩大小 */
|
|
|
|
|
|
compressedSize: number;
|
|
|
|
|
|
/** 解压缩后大小 */
|
|
|
|
|
|
decompressedSize: number;
|
|
|
|
|
|
/** 解压缩耗时 */
|
|
|
|
|
|
decompressionTime: number;
|
|
|
|
|
|
/** 使用的算法 */
|
|
|
|
|
|
algorithm: string;
|
2025-08-14 23:59:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 压缩统计信息
|
|
|
|
|
|
*/
|
|
|
|
|
|
export interface CompressionStats {
|
2025-08-20 10:16:54 +08:00
|
|
|
|
/** 总压缩次数 */
|
|
|
|
|
|
totalCompressions: number;
|
|
|
|
|
|
/** 总解压缩次数 */
|
|
|
|
|
|
totalDecompressions: number;
|
|
|
|
|
|
/** 总原始字节数 */
|
2025-08-14 23:59:00 +08:00
|
|
|
|
totalOriginalBytes: number;
|
2025-08-20 10:16:54 +08:00
|
|
|
|
/** 总压缩字节数 */
|
2025-08-14 23:59:00 +08:00
|
|
|
|
totalCompressedBytes: number;
|
2025-08-20 10:16:54 +08:00
|
|
|
|
/** 平均压缩比 */
|
2025-08-14 23:59:00 +08:00
|
|
|
|
averageCompressionRatio: number;
|
2025-08-20 10:16:54 +08:00
|
|
|
|
/** 平均压缩时间 */
|
2025-08-14 23:59:00 +08:00
|
|
|
|
averageCompressionTime: number;
|
2025-08-20 10:16:54 +08:00
|
|
|
|
/** 平均解压缩时间 */
|
2025-08-14 23:59:00 +08:00
|
|
|
|
averageDecompressionTime: number;
|
2025-08-20 10:16:54 +08:00
|
|
|
|
/** 算法使用统计 */
|
|
|
|
|
|
algorithmUsage: Record<string, number>;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 无压缩算法实现(默认)
|
|
|
|
|
|
*/
|
|
|
|
|
|
export class NoCompressionAlgorithm implements ICompressionAlgorithm {
|
|
|
|
|
|
readonly name = 'none';
|
|
|
|
|
|
readonly version = '1.0.0';
|
|
|
|
|
|
readonly supportsAsync = false;
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
compress(data: ArrayBuffer): ArrayBuffer {
|
|
|
|
|
|
return data.slice(0);
|
|
|
|
|
|
}
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
decompress(data: ArrayBuffer): ArrayBuffer {
|
|
|
|
|
|
return data.slice(0);
|
|
|
|
|
|
}
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
estimateCompressedSize(data: ArrayBuffer): number {
|
|
|
|
|
|
return data.byteLength;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* LZ字符串压缩算法实现
|
|
|
|
|
|
*/
|
|
|
|
|
|
export class LZCompressionAlgorithm implements ICompressionAlgorithm {
|
|
|
|
|
|
readonly name = 'lz-string';
|
|
|
|
|
|
readonly version = '1.0.0';
|
|
|
|
|
|
readonly supportsAsync = false;
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
compress(data: ArrayBuffer): ArrayBuffer {
|
|
|
|
|
|
// 将ArrayBuffer转换为字符串
|
|
|
|
|
|
const decoder = new TextDecoder();
|
|
|
|
|
|
const input = decoder.decode(data);
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
if (!input) {
|
|
|
|
|
|
return data.slice(0);
|
|
|
|
|
|
}
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
// LZ压缩算法
|
|
|
|
|
|
const dictionary: { [key: string]: number } = {};
|
|
|
|
|
|
let dictSize = 256;
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
// 初始化字典
|
|
|
|
|
|
for (let i = 0; i < 256; i++) {
|
|
|
|
|
|
dictionary[String.fromCharCode(i)] = i;
|
|
|
|
|
|
}
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
let w = '';
|
|
|
|
|
|
const result: number[] = [];
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
for (let i = 0; i < input.length; i++) {
|
|
|
|
|
|
const c = input.charAt(i);
|
|
|
|
|
|
const wc = w + c;
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
if (dictionary[wc] !== undefined) {
|
|
|
|
|
|
w = wc;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
result.push(dictionary[w]);
|
|
|
|
|
|
dictionary[wc] = dictSize++;
|
|
|
|
|
|
w = c;
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
// 防止字典过大
|
|
|
|
|
|
if (dictSize >= 0xFFFF) {
|
|
|
|
|
|
dictSize = 256;
|
|
|
|
|
|
// 重置字典
|
|
|
|
|
|
for (const key in dictionary) {
|
|
|
|
|
|
if (dictionary[key] >= 256) {
|
|
|
|
|
|
delete dictionary[key];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
if (w) {
|
|
|
|
|
|
result.push(dictionary[w]);
|
|
|
|
|
|
}
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
// 将结果转换为ArrayBuffer
|
|
|
|
|
|
return this.numbersToArrayBuffer(result);
|
|
|
|
|
|
}
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
decompress(data: ArrayBuffer): ArrayBuffer {
|
|
|
|
|
|
if (data.byteLength === 0) {
|
|
|
|
|
|
return data.slice(0);
|
|
|
|
|
|
}
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
const numbers = this.arrayBufferToNumbers(data);
|
|
|
|
|
|
if (numbers.length === 0) {
|
|
|
|
|
|
return data.slice(0);
|
|
|
|
|
|
}
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
const dictionary: { [key: number]: string } = {};
|
|
|
|
|
|
let dictSize = 256;
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
// 初始化字典
|
|
|
|
|
|
for (let i = 0; i < 256; i++) {
|
|
|
|
|
|
dictionary[i] = String.fromCharCode(i);
|
|
|
|
|
|
}
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
let w = String.fromCharCode(numbers[0]);
|
|
|
|
|
|
const result = [w];
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
for (let i = 1; i < numbers.length; i++) {
|
|
|
|
|
|
const k = numbers[i];
|
|
|
|
|
|
let entry: string;
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
if (dictionary[k] !== undefined) {
|
|
|
|
|
|
entry = dictionary[k];
|
|
|
|
|
|
} else if (k === dictSize) {
|
|
|
|
|
|
entry = w + w.charAt(0);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
throw new Error('LZ解压缩错误:无效的压缩数据');
|
|
|
|
|
|
}
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
result.push(entry);
|
|
|
|
|
|
dictionary[dictSize++] = w + entry.charAt(0);
|
|
|
|
|
|
w = entry;
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
// 防止字典过大
|
|
|
|
|
|
if (dictSize >= 0xFFFF) {
|
|
|
|
|
|
dictSize = 256;
|
|
|
|
|
|
// 重置字典
|
|
|
|
|
|
for (const key in dictionary) {
|
|
|
|
|
|
if (parseInt(key) >= 256) {
|
|
|
|
|
|
delete dictionary[key];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
// 将结果转换为ArrayBuffer
|
|
|
|
|
|
const output = result.join('');
|
|
|
|
|
|
const encoder = new TextEncoder();
|
|
|
|
|
|
return encoder.encode(output).buffer;
|
|
|
|
|
|
}
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
estimateCompressedSize(data: ArrayBuffer): number {
|
|
|
|
|
|
// 简单估算:假设压缩率在30%-70%之间
|
|
|
|
|
|
const size = data.byteLength;
|
|
|
|
|
|
return Math.floor(size * 0.5); // 50%的估算压缩率
|
|
|
|
|
|
}
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 将数字数组转换为ArrayBuffer
|
|
|
|
|
|
*/
|
|
|
|
|
|
private numbersToArrayBuffer(numbers: number[]): ArrayBuffer {
|
|
|
|
|
|
// 使用变长编码以节省空间
|
|
|
|
|
|
const bytes: number[] = [];
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
for (const num of numbers) {
|
|
|
|
|
|
if (num < 128) {
|
|
|
|
|
|
// 小于128,用1字节
|
|
|
|
|
|
bytes.push(num);
|
|
|
|
|
|
} else if (num < 16384) {
|
|
|
|
|
|
// 小于16384,用2字节,最高位为1表示有下一字节
|
|
|
|
|
|
bytes.push(0x80 | (num & 0x7F));
|
|
|
|
|
|
bytes.push((num >> 7) & 0x7F);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 大于等于16384,用3字节
|
|
|
|
|
|
bytes.push(0x80 | (num & 0x7F));
|
|
|
|
|
|
bytes.push(0x80 | ((num >> 7) & 0x7F));
|
|
|
|
|
|
bytes.push((num >> 14) & 0x7F);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
return new Uint8Array(bytes).buffer;
|
|
|
|
|
|
}
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 将ArrayBuffer转换为数字数组
|
|
|
|
|
|
*/
|
|
|
|
|
|
private arrayBufferToNumbers(buffer: ArrayBuffer): number[] {
|
|
|
|
|
|
const bytes = new Uint8Array(buffer);
|
|
|
|
|
|
const numbers: number[] = [];
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
for (let i = 0; i < bytes.length; i++) {
|
|
|
|
|
|
const byte1 = bytes[i];
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
if ((byte1 & 0x80) === 0) {
|
|
|
|
|
|
// 单字节数字
|
|
|
|
|
|
numbers.push(byte1);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 多字节数字
|
|
|
|
|
|
let num = byte1 & 0x7F;
|
|
|
|
|
|
i++;
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
if (i < bytes.length) {
|
|
|
|
|
|
const byte2 = bytes[i];
|
|
|
|
|
|
num |= (byte2 & 0x7F) << 7;
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
if ((byte2 & 0x80) !== 0) {
|
|
|
|
|
|
// 三字节数字
|
|
|
|
|
|
i++;
|
|
|
|
|
|
if (i < bytes.length) {
|
|
|
|
|
|
const byte3 = bytes[i];
|
|
|
|
|
|
num |= (byte3 & 0x7F) << 14;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
numbers.push(num);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
return numbers;
|
|
|
|
|
|
}
|
2025-08-14 23:59:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 消息压缩器
|
|
|
|
|
|
*/
|
|
|
|
|
|
export class MessageCompressor {
|
|
|
|
|
|
private logger = createLogger('MessageCompressor');
|
|
|
|
|
|
private config: CompressionConfig;
|
2025-08-20 10:16:54 +08:00
|
|
|
|
private algorithms = new Map<string, ICompressionAlgorithm>();
|
2025-08-14 23:59:00 +08:00
|
|
|
|
private stats: CompressionStats;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 构造函数
|
|
|
|
|
|
*/
|
|
|
|
|
|
constructor(config: Partial<CompressionConfig> = {}) {
|
|
|
|
|
|
this.config = {
|
2025-08-20 10:16:54 +08:00
|
|
|
|
defaultAlgorithm: 'none',
|
2025-08-14 23:59:00 +08:00
|
|
|
|
threshold: 1024, // 1KB以上才压缩
|
|
|
|
|
|
enableAsync: true,
|
2025-08-20 10:16:54 +08:00
|
|
|
|
level: 6,
|
2025-08-14 23:59:00 +08:00
|
|
|
|
chunkSize: 64 * 1024, // 64KB分块
|
2025-08-20 10:16:54 +08:00
|
|
|
|
enableStats: true,
|
2025-08-14 23:59:00 +08:00
|
|
|
|
...config
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
this.stats = {
|
2025-08-20 10:16:54 +08:00
|
|
|
|
totalCompressions: 0,
|
|
|
|
|
|
totalDecompressions: 0,
|
2025-08-14 23:59:00 +08:00
|
|
|
|
totalOriginalBytes: 0,
|
|
|
|
|
|
totalCompressedBytes: 0,
|
2025-08-20 10:16:54 +08:00
|
|
|
|
averageCompressionRatio: 1.0,
|
2025-08-14 23:59:00 +08:00
|
|
|
|
averageCompressionTime: 0,
|
|
|
|
|
|
averageDecompressionTime: 0,
|
2025-08-20 10:16:54 +08:00
|
|
|
|
algorithmUsage: {}
|
2025-08-14 23:59:00 +08:00
|
|
|
|
};
|
2025-08-20 10:16:54 +08:00
|
|
|
|
|
|
|
|
|
|
// 注册默认算法
|
|
|
|
|
|
this.registerAlgorithm(new NoCompressionAlgorithm());
|
|
|
|
|
|
this.registerAlgorithm(new LZCompressionAlgorithm());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 注册压缩算法
|
|
|
|
|
|
*/
|
|
|
|
|
|
public registerAlgorithm(algorithm: ICompressionAlgorithm): void {
|
|
|
|
|
|
if (this.algorithms.has(algorithm.name)) {
|
|
|
|
|
|
this.logger.warn(`压缩算法 '${algorithm.name}' 已存在,将被覆盖`);
|
|
|
|
|
|
}
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
this.algorithms.set(algorithm.name, algorithm);
|
|
|
|
|
|
this.stats.algorithmUsage[algorithm.name] = 0;
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
this.logger.info(`注册压缩算法: ${algorithm.name} v${algorithm.version}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 注销压缩算法
|
|
|
|
|
|
*/
|
|
|
|
|
|
public unregisterAlgorithm(algorithmName: string): boolean {
|
|
|
|
|
|
if (algorithmName === 'none') {
|
|
|
|
|
|
this.logger.warn('无法注销默认的无压缩算法');
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
const removed = this.algorithms.delete(algorithmName);
|
|
|
|
|
|
if (removed) {
|
|
|
|
|
|
delete this.stats.algorithmUsage[algorithmName];
|
|
|
|
|
|
this.logger.info(`注销压缩算法: ${algorithmName}`);
|
|
|
|
|
|
}
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
return removed;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取已注册的算法列表
|
|
|
|
|
|
*/
|
|
|
|
|
|
public getRegisteredAlgorithms(): string[] {
|
|
|
|
|
|
return Array.from(this.algorithms.keys());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 检查算法是否已注册
|
|
|
|
|
|
*/
|
|
|
|
|
|
public hasAlgorithm(algorithmName: string): boolean {
|
|
|
|
|
|
return this.algorithms.has(algorithmName);
|
2025-08-14 23:59:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 压缩数据
|
|
|
|
|
|
*/
|
2025-08-20 10:16:54 +08:00
|
|
|
|
public async compress(
|
2025-11-02 23:50:41 +08:00
|
|
|
|
data: ArrayBuffer | string,
|
2025-08-20 10:16:54 +08:00
|
|
|
|
algorithmName?: string
|
|
|
|
|
|
): Promise<CompressionResult> {
|
2025-08-14 23:59:00 +08:00
|
|
|
|
const startTime = performance.now();
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
// 转换输入数据
|
2025-11-02 23:50:41 +08:00
|
|
|
|
const inputBuffer = typeof data === 'string'
|
|
|
|
|
|
? new TextEncoder().encode(data).buffer
|
2025-08-20 10:16:54 +08:00
|
|
|
|
: data;
|
|
|
|
|
|
const originalSize = inputBuffer.byteLength;
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
// 选择压缩算法
|
|
|
|
|
|
const selectedAlgorithm = algorithmName || this.config.defaultAlgorithm;
|
|
|
|
|
|
const algorithm = this.algorithms.get(selectedAlgorithm);
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
if (!algorithm) {
|
|
|
|
|
|
throw new Error(`未找到压缩算法: ${selectedAlgorithm}`);
|
|
|
|
|
|
}
|
2025-08-14 23:59:00 +08:00
|
|
|
|
|
|
|
|
|
|
try {
|
2025-08-20 10:16:54 +08:00
|
|
|
|
let compressedData: ArrayBuffer;
|
|
|
|
|
|
let wasCompressed = false;
|
2025-08-14 23:59:00 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
// 检查是否需要压缩
|
|
|
|
|
|
if (originalSize < this.config.threshold || selectedAlgorithm === 'none') {
|
|
|
|
|
|
compressedData = inputBuffer.slice(0);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 选择同步或异步压缩
|
|
|
|
|
|
if (this.config.enableAsync && algorithm.supportsAsync && algorithm.compressAsync) {
|
|
|
|
|
|
compressedData = await algorithm.compressAsync(inputBuffer);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
compressedData = algorithm.compress(inputBuffer);
|
|
|
|
|
|
}
|
|
|
|
|
|
wasCompressed = true;
|
2025-08-14 23:59:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const endTime = performance.now();
|
|
|
|
|
|
const compressionTime = endTime - startTime;
|
2025-08-20 10:16:54 +08:00
|
|
|
|
const compressedSize = compressedData.byteLength;
|
2025-08-14 23:59:00 +08:00
|
|
|
|
const compressionRatio = originalSize > 0 ? compressedSize / originalSize : 1;
|
|
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
// 更新统计信息
|
|
|
|
|
|
if (this.config.enableStats) {
|
|
|
|
|
|
this.updateCompressionStats(
|
2025-11-02 23:50:41 +08:00
|
|
|
|
selectedAlgorithm,
|
|
|
|
|
|
originalSize,
|
|
|
|
|
|
compressedSize,
|
2025-08-20 10:16:54 +08:00
|
|
|
|
compressionTime
|
|
|
|
|
|
);
|
2025-08-14 23:59:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const result: CompressionResult = {
|
|
|
|
|
|
data: compressedData,
|
|
|
|
|
|
originalSize,
|
|
|
|
|
|
compressedSize,
|
|
|
|
|
|
compressionRatio,
|
|
|
|
|
|
compressionTime,
|
2025-08-20 10:16:54 +08:00
|
|
|
|
algorithm: selectedAlgorithm,
|
|
|
|
|
|
wasCompressed
|
2025-08-14 23:59:00 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
this.logger.debug(
|
|
|
|
|
|
`压缩完成: ${originalSize}B -> ${compressedSize}B ` +
|
|
|
|
|
|
`(${(compressionRatio * 100).toFixed(1)}%) ` +
|
|
|
|
|
|
`用时 ${compressionTime.toFixed(2)}ms, 算法: ${selectedAlgorithm}`
|
|
|
|
|
|
);
|
2025-08-14 23:59:00 +08:00
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
2025-08-20 10:16:54 +08:00
|
|
|
|
this.logger.error(`压缩失败 (${selectedAlgorithm}):`, error);
|
|
|
|
|
|
throw error;
|
2025-08-14 23:59:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 解压缩数据
|
|
|
|
|
|
*/
|
2025-08-20 10:16:54 +08:00
|
|
|
|
public async decompress(
|
2025-11-02 23:50:41 +08:00
|
|
|
|
data: ArrayBuffer,
|
2025-08-20 10:16:54 +08:00
|
|
|
|
algorithmName: string
|
|
|
|
|
|
): Promise<DecompressionResult> {
|
2025-08-14 23:59:00 +08:00
|
|
|
|
const startTime = performance.now();
|
2025-08-20 10:16:54 +08:00
|
|
|
|
const compressedSize = data.byteLength;
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
const algorithm = this.algorithms.get(algorithmName);
|
|
|
|
|
|
if (!algorithm) {
|
|
|
|
|
|
throw new Error(`未找到解压缩算法: ${algorithmName}`);
|
|
|
|
|
|
}
|
2025-08-14 23:59:00 +08:00
|
|
|
|
|
|
|
|
|
|
try {
|
2025-08-20 10:16:54 +08:00
|
|
|
|
let decompressedData: ArrayBuffer;
|
2025-08-14 23:59:00 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
// 选择同步或异步解压缩
|
|
|
|
|
|
if (this.config.enableAsync && algorithm.supportsAsync && algorithm.decompressAsync) {
|
|
|
|
|
|
decompressedData = await algorithm.decompressAsync(data);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
decompressedData = algorithm.decompress(data);
|
2025-08-14 23:59:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const endTime = performance.now();
|
|
|
|
|
|
const decompressionTime = endTime - startTime;
|
2025-08-20 10:16:54 +08:00
|
|
|
|
const decompressedSize = decompressedData.byteLength;
|
2025-08-14 23:59:00 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
// 更新统计信息
|
|
|
|
|
|
if (this.config.enableStats) {
|
|
|
|
|
|
this.updateDecompressionStats(algorithmName, decompressionTime);
|
|
|
|
|
|
}
|
2025-08-14 23:59:00 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
const result: DecompressionResult = {
|
|
|
|
|
|
data: decompressedData,
|
|
|
|
|
|
compressedSize,
|
|
|
|
|
|
decompressedSize,
|
|
|
|
|
|
decompressionTime,
|
|
|
|
|
|
algorithm: algorithmName
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
this.logger.debug(
|
|
|
|
|
|
`解压缩完成: ${compressedSize}B -> ${decompressedSize}B ` +
|
|
|
|
|
|
`用时 ${decompressionTime.toFixed(2)}ms, 算法: ${algorithmName}`
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
2025-08-14 23:59:00 +08:00
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
2025-08-20 10:16:54 +08:00
|
|
|
|
this.logger.error(`解压缩失败 (${algorithmName}):`, error);
|
2025-08-14 23:59:00 +08:00
|
|
|
|
throw error;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-20 10:16:54 +08:00
|
|
|
|
* 估算压缩后大小
|
2025-08-14 23:59:00 +08:00
|
|
|
|
*/
|
2025-08-20 10:16:54 +08:00
|
|
|
|
public estimateCompressedSize(
|
2025-11-02 23:50:41 +08:00
|
|
|
|
data: ArrayBuffer,
|
2025-08-20 10:16:54 +08:00
|
|
|
|
algorithmName?: string
|
|
|
|
|
|
): number {
|
|
|
|
|
|
const selectedAlgorithm = algorithmName || this.config.defaultAlgorithm;
|
|
|
|
|
|
const algorithm = this.algorithms.get(selectedAlgorithm);
|
2025-11-02 23:50:41 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
if (!algorithm) {
|
|
|
|
|
|
return data.byteLength;
|
2025-08-14 23:59:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
if (algorithm.estimateCompressedSize) {
|
|
|
|
|
|
return algorithm.estimateCompressedSize(data);
|
2025-08-14 23:59:00 +08:00
|
|
|
|
}
|
2025-08-20 10:16:54 +08:00
|
|
|
|
|
|
|
|
|
|
// 如果没有估算函数,返回原始大小
|
|
|
|
|
|
return data.byteLength;
|
2025-08-14 23:59:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-20 10:16:54 +08:00
|
|
|
|
* 获取压缩统计信息
|
2025-08-14 23:59:00 +08:00
|
|
|
|
*/
|
2025-08-20 10:16:54 +08:00
|
|
|
|
public getStats(): CompressionStats {
|
2025-08-14 23:59:00 +08:00
|
|
|
|
return { ...this.stats };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 重置统计信息
|
|
|
|
|
|
*/
|
2025-08-20 10:16:54 +08:00
|
|
|
|
public resetStats(): void {
|
2025-08-14 23:59:00 +08:00
|
|
|
|
this.stats = {
|
2025-08-20 10:16:54 +08:00
|
|
|
|
totalCompressions: 0,
|
|
|
|
|
|
totalDecompressions: 0,
|
2025-08-14 23:59:00 +08:00
|
|
|
|
totalOriginalBytes: 0,
|
|
|
|
|
|
totalCompressedBytes: 0,
|
2025-08-20 10:16:54 +08:00
|
|
|
|
averageCompressionRatio: 1.0,
|
2025-08-14 23:59:00 +08:00
|
|
|
|
averageCompressionTime: 0,
|
|
|
|
|
|
averageDecompressionTime: 0,
|
2025-08-20 10:16:54 +08:00
|
|
|
|
algorithmUsage: {}
|
2025-08-14 23:59:00 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
// 重新初始化算法使用统计
|
|
|
|
|
|
for (const algorithmName of this.algorithms.keys()) {
|
|
|
|
|
|
this.stats.algorithmUsage[algorithmName] = 0;
|
2025-08-14 23:59:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-20 10:16:54 +08:00
|
|
|
|
* 更新配置
|
2025-08-14 23:59:00 +08:00
|
|
|
|
*/
|
2025-08-20 10:16:54 +08:00
|
|
|
|
public updateConfig(newConfig: Partial<CompressionConfig>): void {
|
|
|
|
|
|
Object.assign(this.config, newConfig);
|
|
|
|
|
|
this.logger.info('压缩器配置已更新');
|
2025-08-14 23:59:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-20 10:16:54 +08:00
|
|
|
|
* 获取配置
|
2025-08-14 23:59:00 +08:00
|
|
|
|
*/
|
2025-08-20 10:16:54 +08:00
|
|
|
|
public getConfig(): CompressionConfig {
|
|
|
|
|
|
return { ...this.config };
|
2025-08-14 23:59:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-20 10:16:54 +08:00
|
|
|
|
* 更新压缩统计信息
|
2025-08-14 23:59:00 +08:00
|
|
|
|
*/
|
2025-08-20 10:16:54 +08:00
|
|
|
|
private updateCompressionStats(
|
2025-11-02 23:50:41 +08:00
|
|
|
|
algorithmName: string,
|
|
|
|
|
|
originalSize: number,
|
|
|
|
|
|
compressedSize: number,
|
2025-08-20 10:16:54 +08:00
|
|
|
|
compressionTime: number
|
|
|
|
|
|
): void {
|
|
|
|
|
|
this.stats.totalCompressions++;
|
|
|
|
|
|
this.stats.totalOriginalBytes += originalSize;
|
|
|
|
|
|
this.stats.totalCompressedBytes += compressedSize;
|
|
|
|
|
|
this.stats.algorithmUsage[algorithmName]++;
|
|
|
|
|
|
|
|
|
|
|
|
// 更新平均压缩比
|
2025-11-02 23:50:41 +08:00
|
|
|
|
this.stats.averageCompressionRatio = this.stats.totalOriginalBytes > 0
|
|
|
|
|
|
? this.stats.totalCompressedBytes / this.stats.totalOriginalBytes
|
2025-08-20 10:16:54 +08:00
|
|
|
|
: 1.0;
|
|
|
|
|
|
|
|
|
|
|
|
// 更新平均压缩时间
|
2025-11-02 23:50:41 +08:00
|
|
|
|
this.stats.averageCompressionTime =
|
|
|
|
|
|
(this.stats.averageCompressionTime * (this.stats.totalCompressions - 1) + compressionTime)
|
2025-08-20 10:16:54 +08:00
|
|
|
|
/ this.stats.totalCompressions;
|
2025-08-14 23:59:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-20 10:16:54 +08:00
|
|
|
|
* 更新解压缩统计信息
|
2025-08-14 23:59:00 +08:00
|
|
|
|
*/
|
2025-08-20 10:16:54 +08:00
|
|
|
|
private updateDecompressionStats(algorithmName: string, decompressionTime: number): void {
|
|
|
|
|
|
this.stats.totalDecompressions++;
|
2025-08-14 23:59:00 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
// 更新平均解压缩时间
|
2025-11-02 23:50:41 +08:00
|
|
|
|
this.stats.averageDecompressionTime =
|
|
|
|
|
|
(this.stats.averageDecompressionTime * (this.stats.totalDecompressions - 1) + decompressionTime)
|
2025-08-20 10:16:54 +08:00
|
|
|
|
/ this.stats.totalDecompressions;
|
2025-08-14 23:59:00 +08:00
|
|
|
|
}
|
2025-08-20 10:16:54 +08:00
|
|
|
|
}
|
2025-08-14 23:59:00 +08:00
|
|
|
|
|
2025-08-20 10:16:54 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 全局压缩器实例
|
|
|
|
|
|
*/
|
2025-11-02 23:50:41 +08:00
|
|
|
|
export const globalCompressor = new MessageCompressor();
|