Files
youlegames/codes/games/server/docs/guides/development/07-工具模块.md
2026-02-04 23:47:45 +08:00

37 KiB
Raw Blame History

工具模块详解

📋 文档概述

本文档详细说明进贤麻将的工具模块系统,包括:

  • Logger.js - 日志管理器
  • ErrorHandler.js - 错误处理器
  • shared/utils - 共享工具类
  • 工具模块的使用方法和最佳实践

文档目标:帮助开发者正确使用日志系统和错误处理机制,提升代码质量和可维护性。


🎯 工具模块架构

系统组成

工具模块系统
├── utils/                    # 子游戏专用工具
│   ├── Logger.js            # 日志管理器
│   └── ErrorHandler.js      # 错误处理器
│
└── shared/utils/            # 前后端共享工具
    ├── ArrayUtils.js        # 数组操作工具
    ├── CardSourceInfoHelper.js     # 牌源信息辅助
    ├── GameContextHelper.js        # 游戏上下文辅助
    ├── GameStateHelper.js          # 游戏状态辅助
    ├── MahjongCardUniqueId.js      # 麻将牌唯一ID
    └── RoomConfigUtils.js          # 房间配置工具

设计原则

  1. 分离关注点:日志和错误处理分开管理
  2. ES5兼容兼容浏览器和Node.js环境
  3. 统一接口提供统一的API调用方式
  4. 性能优化:最小化性能开销
  5. 易于使用简单直观的API设计

📝 Logger.js - 日志管理器

核心功能

文件位置server/games2/jinxianmahjong/utils/Logger.js
版本v1.0.0
总行数606行

Logger提供了完整的日志管理功能

  • 6级日志系统TRACE/DEBUG/INFO/WARN/ERROR/FATAL
  • 日志格式化和美化输出
  • 日志历史记录和查询
  • 日志统计分析
  • 性能计时功能
  • 分组日志输出
  • 控制台颜色支持

日志级别定义

LOG_LEVELS: {
  TRACE: 0,       // 最详细的信息,用于跟踪程序执行
  DEBUG: 1,       // 调试信息,开发时使用
  INFO: 2,        // 一般信息,记录程序运行状态
  WARN: 3,        // 警告信息,可能的问题
  ERROR: 4,       // 错误信息,程序运行错误
  FATAL: 5        // 致命错误,程序无法继续运行
}

级别控制:只有 >= currentLevel 的日志才会输出

颜色编码

级别 颜色 说明
TRACE 白色 详细跟踪信息
DEBUG 青色 开发调试信息
INFO 绿色 正常运行信息
WARN 黄色 警告提示信息
ERROR 红色 错误异常信息
FATAL 紫色 致命错误信息

配置参数

config: {
  // 当前日志级别
  currentLevel: 1,            // 默认DEBUG级别
  
  // 输出设置
  enableConsole: true,        // 启用控制台输出
  enableColors: true,         // 启用颜色(如果支持)
  enableTimestamp: true,      // 启用时间戳
  enableModuleName: true,     // 启用模块名
  enableStackTrace: false,    // 启用堆栈跟踪ERROR级别以上
  
  // 格式设置
  timestampFormat: 'ISO',     // 时间戳格式: 'ISO', 'LOCAL', 'TIMESTAMP'
  moduleName: 'JinXianMahjong', // 模块名
  
  // 性能设置
  maxLogHistory: 1000,        // 最大日志历史记录数
  flushInterval: 0            // 刷新间隔毫秒0表示立即输出
}

核心API

1. 设置日志级别

/**
 * 设置日志级别
 * @param {number|string} level - 日志级别(数字或字符串)
 */
Logger.setLevel(level)

// 使用示例
Logger.setLevel('DEBUG');     // 使用字符串
Logger.setLevel(1);           // 使用数字
Logger.setLevel('INFO');      // 只输出INFO及以上级别

2. 记录日志

基础日志方法

/**
 * 记录日志
 * @param {string} tag - 标签(模块/功能名称)
 * @param {string} message - 日志消息
 * @param {Object} data - 附加数据(可选)
 */
Logger.trace(tag, message, data)
Logger.debug(tag, message, data)
Logger.info(tag, message, data)
Logger.warn(tag, message, data)
Logger.error(tag, message, data)
Logger.fatal(tag, message, data)

使用示例

// 基础日志
Logger.info('GameController', '游戏开始');

// 带附加数据的日志
Logger.debug('MahjongService', '发牌完成', {
  playerCount: 4,
  tilesPerPlayer: 13
});

// 警告日志
Logger.warn('OperationManager', '玩家操作超时', {
  playerId: 'player001',
  operation: 'discard',
  timeout: 15000
});

// 错误日志
Logger.error('RpcHandler', 'RPC调用失败', {
  method: 'player_discard',
  errorCode: 2001,
  error: error.message
});

// 致命错误
Logger.fatal('System', '系统初始化失败', {
  reason: '依赖模块加载失败'
});

3. 分组日志

/**
 * 分组日志(用于组织相关日志)
 */
Logger.group(groupName)         // 开始分组
Logger.groupEnd()                // 结束分组
Logger.groupCollapsed(groupName) // 开始折叠分组(默认折叠)

// 使用示例
Logger.group('游戏初始化');
  Logger.info('Init', '加载配置');
  Logger.info('Init', '创建游戏状态');
  Logger.info('Init', '初始化牌墙');
Logger.groupEnd();

4. 性能计时

/**
 * 性能计时
 */
Logger.time(label)      // 开始计时
Logger.timeEnd(label)   // 结束计时并输出

// 使用示例
Logger.time('胡牌检测');
var result = WinDetectionFactory.detectWin(handTiles, jingInfo);
Logger.timeEnd('胡牌检测');  // 输出: 胡牌检测: 3.142ms

5. 表格输出

/**
 * 表格输出(用于输出结构化数据)
 * @param {Array|Object} data - 表格数据
 */
Logger.table(data)

// 使用示例
var players = [
  { id: 'p1', name: '玩家1', score: 100 },
  { id: 'p2', name: '玩家2', score: 50 },
  { id: 'p3', name: '玩家3', score: -30 },
  { id: 'p4', name: '玩家4', score: -20 }
];
Logger.table(players);

6. 日志历史

/**
 * 获取日志历史
 * @param {number} limit - 返回数量限制(可选)
 * @param {number} level - 过滤日志级别(可选)
 * @returns {Array} 日志历史数组
 */
Logger.getHistory(limit, level)

// 使用示例
var allLogs = Logger.getHistory();           // 获取所有日志
var last100 = Logger.getHistory(100);        // 获取最近100条
var errors = Logger.getHistory(null, Logger.LOG_LEVELS.ERROR); // 只获取错误日志

7. 清空历史

/**
 * 清空日志历史
 */
Logger.clearHistory()

8. 日志统计

/**
 * 获取日志统计信息
 * @returns {Object} 统计对象
 */
Logger.getStatistics()

// 返回示例
{
  total: 1523,              // 总日志数
  byLevel: {                // 按级别统计
    0: 234,                 // TRACE
    1: 567,                 // DEBUG
    2: 456,                 // INFO
    3: 123,                 // WARN
    4: 89,                  // ERROR
    5: 54                   // FATAL
  },
  startTime: 1697376000000, // 统计开始时间
  duration: 3600000,        // 运行时长(毫秒)
  rate: 0.42                // 日志频率(条/秒)
}

9. 重置统计

/**
 * 重置日志统计
 */
Logger.resetStatistics()

10. 配置管理

/**
 * 配置Logger
 * @param {Object} options - 配置选项
 */
Logger.configure(options)

// 使用示例
Logger.configure({
  currentLevel: Logger.LOG_LEVELS.INFO,  // 设置为INFO级别
  enableTimestamp: true,                 // 启用时间戳
  enableColors: true,                    // 启用颜色
  timestampFormat: 'ISO',                // 使用ISO格式
  maxLogHistory: 500                     // 最多保存500条历史
});

日志格式

标准格式

[时间戳] [级别] [模块名:标签] 消息内容

输出示例

[2025-10-15T10:30:45.123Z] [INFO] [JinXianMahjong:GameController] 游戏开始
[2025-10-15T10:30:45.456Z] [DEBUG] [JinXianMahjong:MahjongService] 发牌完成 { playerCount: 4, tilesPerPlayer: 13 }
[2025-10-15T10:30:46.789Z] [WARN] [JinXianMahjong:OperationManager] 玩家操作超时 { playerId: 'player001', timeout: 15000 }
[2025-10-15T10:30:47.012Z] [ERROR] [JinXianMahjong:RpcHandler] RPC调用失败 { method: 'player_discard', errorCode: 2001 }

实际使用场景

场景1游戏流程日志

// GameController.js

GameController.prototype.startGame = function() {
  Logger.info('GameController', '======= 开始新游戏 =======');
  
  Logger.group('游戏初始化');
  Logger.debug('GameController', '玩家数量:', this.players.length);
  Logger.debug('GameController', '房间配置:', this.roomtype);
  
  try {
    // 初始化游戏状态
    Logger.time('初始化游戏状态');
    this.gameState = GameStateManager.createGameState(
      this.roomcode,
      this.roomtype,
      this.getPlayerIds()
    );
    Logger.timeEnd('初始化游戏状态');
    
    Logger.info('GameController', '游戏状态创建成功');
    
    // 初始化牌墙
    Logger.time('初始化牌墙');
    this.mahjongWall = MahjongWall.initialize(this.gameState);
    Logger.timeEnd('初始化牌墙');
    
    Logger.info('GameController', '牌墙初始化完成');
    
    // 发牌
    Logger.time('发牌');
    this.dealTiles();
    Logger.timeEnd('发牌');
    
    Logger.info('GameController', '发牌完成');
    
  } catch (error) {
    Logger.error('GameController', '游戏初始化失败', {
      error: error.message,
      stack: error.stack
    });
    throw error;
  }
  
  Logger.groupEnd();
  Logger.info('GameController', '======= 游戏初始化完成 =======');
};

场景2调试日志

// MahjongGameService.js

MahjongGameService.prototype.checkWinCondition = function(playerHand, gameState) {
  Logger.debug('MahjongGameService', '开始检查胡牌条件', {
    playerId: playerHand.playerId,
    handSize: playerHand.tiles.length
  });
  
  var rules = gameState.rules;
  Logger.trace('MahjongGameService', '规则配置', {
    useJing: rules.gameRules.useJing,
    minFan: rules.winConditions.minFan
  });
  
  // 胡牌检测
  Logger.time('胡牌检测');
  var winResult = WinDetectionFactory.detectWin(playerHand, gameState.jingInfo);
  Logger.timeEnd('胡牌检测');
  
  Logger.debug('MahjongGameService', '胡牌检测结果', {
    canWin: winResult.canWin,
    patterns: winResult.patterns
  });
  
  if (!winResult.canWin) {
    Logger.debug('MahjongGameService', '不满足胡牌条件');
    return { canWin: false };
  }
  
  // 检查分数
  var score = ScoreCalculation.calculateWinScore(
    winResult.patterns,
    gameState.jingInfo,
    gameState
  );
  
  Logger.debug('MahjongGameService', '计算得分', {
    totalScore: score.totalScore,
    minFan: rules.winConditions.minFan
  });
  
  if (score.totalScore < rules.winConditions.minFan) {
    Logger.warn('MahjongGameService', '未达到起胡分数要求', {
      currentScore: score.totalScore,
      required: rules.winConditions.minFan
    });
    return {
      canWin: false,
      reason: '未达到起胡分数要求(需' + rules.winConditions.minFan + '分)'
    };
  }
  
  Logger.info('MahjongGameService', '胡牌条件满足', {
    playerId: playerHand.playerId,
    score: score.totalScore
  });
  
  return {
    canWin: true,
    patterns: winResult.patterns,
    score: score
  };
};

场景3错误日志

// RpcHandler.js

RpcHandler.prototype.handleRequest = function(pack, room, callback) {
  var method = pack.cmd;
  
  Logger.info('RpcHandler', 'RPC请求', {
    method: method,
    playerId: pack.playerid
  });
  
  try {
    // 验证玩家
    if (!this._validatePlayer(pack.playerid, room)) {
      Logger.warn('RpcHandler', '玩家验证失败', {
        playerId: pack.playerid,
        roomcode: room.roomcode
      });
      return callback({ error: '玩家不存在' });
    }
    
    // 执行RPC方法
    var result = this._executeMethod(method, pack, room);
    
    Logger.debug('RpcHandler', 'RPC执行成功', {
      method: method,
      result: result
    });
    
    callback(result);
    
  } catch (error) {
    Logger.error('RpcHandler', 'RPC执行失败', {
      method: method,
      playerId: pack.playerid,
      error: error.message,
      stack: error.stack
    });
    
    callback({ error: '处理请求失败' });
  }
};

⚠️ ErrorHandler.js - 错误处理器

核心功能

文件位置server/games2/jinxianmahjong/utils/ErrorHandler.js
版本v1.0.0
总行数659行

ErrorHandler提供了完整的错误管理功能

  • 标准化错误代码系统
  • 错误分类和级别管理
  • 错误创建和格式化
  • 错误统计和追踪
  • 安全执行包装
  • 错误恢复策略

错误代码范围

ERROR_CODE_RANGES: {
  CLIENT_START: 1000,           // 客户端/接口错误
  CLIENT_END: 1999,
  
  VALIDATION_START: 2000,       // 数据验证错误
  VALIDATION_END: 2999,
  
  GAME_LOGIC_START: 3000,       // 游戏逻辑错误
  GAME_LOGIC_END: 3999,
  
  BUSINESS_START: 4000,         // 业务逻辑错误
  BUSINESS_END: 4999,
  
  NETWORK_START: 5000,          // 网络通信错误
  NETWORK_END: 5999,
  
  SYSTEM_START: 9000            // 系统错误
}

错误代码定义

1. 框架接口错误1000-1999

INTERFACE_INVALID_PARAMS: 1001,        // 接口参数无效
INTERFACE_ROOM_NOT_FOUND: 1002,        // 房间不存在
INTERFACE_PLAYER_NOT_FOUND: 1003,      // 玩家不存在
INTERFACE_CALLBACK_MISSING: 1004,      // 回调函数缺失
INTERFACE_METHOD_NOT_IMPLEMENTED: 1005, // 接口方法未实现
INTERFACE_TIMEOUT: 1006,               // 接口调用超时
INTERFACE_UNAUTHORIZED: 1007,          // 接口未授权
INTERFACE_RATE_LIMITED: 1008,          // 接口调用频率限制

2. 游戏逻辑错误2000-2999

GAME_INVALID_ACTION: 2001,             // 无效的游戏操作
GAME_WRONG_PHASE: 2002,                // 游戏阶段错误
GAME_PLAYER_LIMIT: 2003,               // 玩家数量限制
GAME_ROOM_FULL: 2004,                  // 房间已满
GAME_ALREADY_STARTED: 2005,            // 游戏已开始
GAME_NOT_STARTED: 2006,                // 游戏未开始
GAME_INVALID_CARD: 2007,               // 无效牌张
GAME_INVALID_OPERATION: 2008,          // 无效操作
GAME_TIMEOUT: 2009,                    // 游戏操作超时
GAME_INSUFFICIENT_PLAYERS: 2010,       // 玩家不足

3. 数据验证错误3000-3999

VALIDATION_ROOMTYPE: 3001,             // RoomType格式错误
VALIDATION_CARD_DATA: 3002,            // 牌张数据无效
VALIDATION_PLAYER_DATA: 3003,          // 玩家数据无效
VALIDATION_ROOM_DATA: 3004,            // 房间数据无效
VALIDATION_GAME_STATE: 3005,           // 游戏状态无效
VALIDATION_CONFIG: 3006,               // 配置数据无效
VALIDATION_MISSING_REQUIRED: 3007,     // 缺少必需字段
VALIDATION_TYPE_MISMATCH: 3008,        // 数据类型不匹配

4. 业务逻辑错误4000-4999

BUSINESS_ROOM_CLOSED: 4001,            // 房间已关闭
BUSINESS_PLAYER_OFFLINE: 4002,         // 玩家离线
BUSINESS_INSUFFICIENT_PERMISSION: 4003, // 权限不足
BUSINESS_DUPLICATE_ACTION: 4004,       // 重复操作
BUSINESS_INVALID_STATE: 4005,          // 无效状态
BUSINESS_RESOURCE_UNAVAILABLE: 4006,   // 资源不可用

5. 网络通信错误5000-5999

NETWORK_CONNECTION_LOST: 5001,         // 连接丢失
NETWORK_TIMEOUT: 5002,                 // 网络超时
NETWORK_INVALID_RESPONSE: 5003,        // 无效响应
NETWORK_SERVER_ERROR: 5004,            // 服务器错误

6. 系统错误9000-9999

SYSTEM_UNKNOWN: 9001,                  // 未知系统错误
SYSTEM_TIMEOUT: 9002,                  // 系统超时
SYSTEM_MEMORY_ERROR: 9003,             // 内存错误
SYSTEM_CONFIGURATION_ERROR: 9004,      // 配置错误
SYSTEM_INITIALIZATION_FAILED: 9005,    // 初始化失败
SYSTEM_DEPENDENCY_ERROR: 9006          // 依赖错误

错误严重级别

ERROR_LEVELS: {
  FATAL: 'FATAL',     // 致命错误,系统无法继续运行
  ERROR: 'ERROR',     // 错误,功能无法正常执行
  WARN: 'WARN',       // 警告,可能影响功能
  INFO: 'INFO'        // 信息,仅用于记录
}

核心API

1. 创建错误对象

/**
 * 创建错误对象
 * @param {number} code - 错误代码
 * @param {string} message - 自定义错误消息(可选)
 * @param {Object} context - 错误上下文信息(可选)
 * @param {string} level - 错误级别(可选)
 * @returns {Object} 标准化错误对象
 */
ErrorHandler.createError(code, message, context, level)

// 使用示例
var error = ErrorHandler.createError(
  ErrorHandler.ERROR_CODES.GAME_INVALID_ACTION,
  '当前阶段不允许出牌',
  {
    playerId: 'player001',
    currentPhase: 'WAITING',
    expectedPhase: 'PLAYING'
  }
);

// 返回的错误对象
{
  code: 2001,
  message: '当前阶段不允许出牌',
  level: 'ERROR',
  timestamp: 1697376000000,
  context: {
    playerId: 'player001',
    currentPhase: 'WAITING',
    expectedPhase: 'PLAYING'
  },
  stack: '...堆栈信息...',
  module: 'JinXianMahjong',
  version: '1.0.0',
  category: 'GAME_LOGIC'
}

2. 安全执行函数

/**
 * 包装函数执行,自动捕获错误
 * @param {Function} func - 要执行的函数
 * @param {Object} context - 执行上下文
 * @param {Array} args - 函数参数
 * @returns {Object} 执行结果 {success: boolean, result: any, error: Object}
 */
ErrorHandler.safeExecute(func, context, args)

// 使用示例
var result = ErrorHandler.safeExecute(
  function() {
    return this.performRiskyOperation();
  },
  gameController,
  []
);

if (result.success) {
  console.log('操作成功:', result.result);
} else {
  console.error('操作失败:', result.error);
}

3. 格式化错误信息

/**
 * 格式化错误信息用于显示
 * @param {Object} error - 错误对象
 * @returns {string} 格式化后的错误信息
 */
ErrorHandler.formatErrorMessage(error)

// 使用示例
var error = ErrorHandler.createError(2001, '无效操作');
var message = ErrorHandler.formatErrorMessage(error);
// 输出: "[ERROR:2001] 无效操作"

4. 错误恢复

/**
 * 尝试从错误中恢复
 * @param {Object} error - 错误对象
 * @param {Function} recoveryFunc - 恢复函数
 * @returns {boolean} 是否成功恢复
 */
ErrorHandler.tryRecover(error, recoveryFunc)

// 使用示例
var recovered = ErrorHandler.tryRecover(error, function() {
  // 尝试恢复逻辑
  return game.resetToLastValidState();
});

if (recovered) {
  Logger.info('ErrorHandler', '已成功从错误中恢复');
} else {
  Logger.error('ErrorHandler', '无法从错误中恢复');
}

5. 错误统计

/**
 * 获取错误统计信息
 * @returns {Object} 统计对象
 */
ErrorHandler.getStatistics()

// 返回示例
{
  total: 45,
  byCode: {
    2001: 12,
    3001: 8,
    4001: 5
  },
  byLevel: {
    FATAL: 1,
    ERROR: 25,
    WARN: 15,
    INFO: 4
  },
  recent: [...]  // 最近100条错误
}

实际使用场景

场景1接口参数验证

// export.js - Export.makewar()

export.makewar = function(room, roomtype) {
  try {
    // 参数验证
    if (!room) {
      throw ErrorHandler.createError(
        ErrorHandler.ERROR_CODES.INTERFACE_INVALID_PARAMS,
        'room参数不能为空',
        { functionName: 'makewar' }
      );
    }
    
    if (!roomtype) {
      throw ErrorHandler.createError(
        ErrorHandler.ERROR_CODES.INTERFACE_INVALID_PARAMS,
        'roomtype参数不能为空',
        { functionName: 'makewar' }
      );
    }
    
    // 验证roomtype格式
    var validation = RoomConfigUtils.validate(roomtype);
    if (!validation.isValid) {
      throw ErrorHandler.createError(
        ErrorHandler.ERROR_CODES.VALIDATION_ROOMTYPE,
        'roomtype格式错误: ' + validation.errors.join(', '),
        { roomtype: roomtype, errors: validation.errors }
      );
    }
    
    // 创建游戏
    Logger.info('Export', '开始创建游戏');
    var gameState = GameStateManager.createGameState(
      room.roomcode,
      roomtype,
      room.getPlayerIds()
    );
    
    return { success: true, gameState: gameState };
    
  } catch (error) {
    Logger.error('Export', 'makewar失败', {
      error: error.message,
      code: error.code
    });
    
    return {
      success: false,
      error: ErrorHandler.formatErrorMessage(error)
    };
  }
};

场景2游戏逻辑错误处理

// OperationManager.js

OperationManager.prototype.validateDiscard = function(playerId, tileCode) {
  // 检查游戏是否开始
  if (this.gameState.phase !== 'PLAYING') {
    return ErrorHandler.createError(
      ErrorHandler.ERROR_CODES.GAME_WRONG_PHASE,
      '游戏未在进行中',
      {
        currentPhase: this.gameState.phase,
        expectedPhase: 'PLAYING'
      }
    );
  }
  
  // 检查是否轮到该玩家
  if (this.gameState.currentPlayerIndex !== this._getPlayerIndex(playerId)) {
    return ErrorHandler.createError(
      ErrorHandler.ERROR_CODES.GAME_INVALID_ACTION,
      '不是该玩家的回合',
      {
        playerId: playerId,
        currentPlayer: this.gameState.players[this.gameState.currentPlayerIndex].id
      }
    );
  }
  
  // 检查玩家是否持有该牌
  var player = this._getPlayer(playerId);
  if (!this._hasCard(player.handTiles, tileCode)) {
    return ErrorHandler.createError(
      ErrorHandler.ERROR_CODES.GAME_INVALID_CARD,
      '玩家手中没有该牌',
      {
        playerId: playerId,
        tileCode: tileCode,
        handTiles: player.handTiles.map(function(t) { return t.code; })
      }
    );
  }
  
  // 验证通过
  return null;
};

// 使用验证结果
OperationManager.prototype.performDiscard = function(playerId, tileCode) {
  var validationError = this.validateDiscard(playerId, tileCode);
  
  if (validationError) {
    Logger.warn('OperationManager', '出牌验证失败', {
      error: validationError.message,
      code: validationError.code
    });
    return {
      success: false,
      error: validationError
    };
  }
  
  // 执行出牌操作
  // ...
  
  return { success: true };
};

场景3安全执行包装

// GameController.js

GameController.prototype.processPlayerAction = function(playerId, action) {
  Logger.info('GameController', '处理玩家操作', {
    playerId: playerId,
    action: action.type
  });
  
  // 使用安全执行包装
  var result = ErrorHandler.safeExecute(
    function() {
      // 可能抛出异常的代码
      return this._executeAction(playerId, action);
    },
    this,
    []
  );
  
  if (!result.success) {
    // 操作失败,记录错误
    Logger.error('GameController', '操作执行失败', {
      playerId: playerId,
      action: action.type,
      error: result.error.message
    });
    
    // 尝试恢复
    var recovered = ErrorHandler.tryRecover(result.error, function() {
      // 恢复到上一个有效状态
      return this._rollbackToLastState();
    }.bind(this));
    
    if (recovered) {
      Logger.info('GameController', '已成功从错误中恢复');
    }
    
    return {
      success: false,
      error: ErrorHandler.formatErrorMessage(result.error)
    };
  }
  
  Logger.debug('GameController', '操作执行成功', {
    playerId: playerId,
    result: result.result
  });
  
  return {
    success: true,
    result: result.result
  };
};

🛠️ shared/utils - 共享工具类

ArrayUtils.js - 数组操作工具

功能:提供数组操作的辅助方法

常用方法

// 数组去重
ArrayUtils.unique(array)

// 数组打乱(洗牌)
ArrayUtils.shuffle(array)

// 数组查找
ArrayUtils.findIndex(array, predicate)

// 数组分组
ArrayUtils.groupBy(array, keyFunc)

CardSourceInfoHelper.js - 牌源信息辅助

功能:管理麻将牌的来源信息

主要方法

// 创建牌源信息
CardSourceInfoHelper.create(source, round, turn)

// 验证牌源信息
CardSourceInfoHelper.validate(sourceInfo)

GameContextHelper.js - 游戏上下文辅助

功能:管理游戏上下文信息

主要方法

// 创建游戏上下文
GameContextHelper.create(roomcode, round, phase)

// 更新上下文
GameContextHelper.update(context, updates)

GameStateHelper.js - 游戏状态辅助

功能:游戏状态的辅助操作

主要方法

// 验证游戏状态
GameStateHelper.validate(gameState)

// 克隆游戏状态
GameStateHelper.clone(gameState)

// 比较游戏状态
GameStateHelper.compare(state1, state2)

MahjongCardUniqueId.js - 麻将牌唯一ID

功能:生成和管理麻将牌的唯一标识

主要方法

// 生成唯一ID10-145
MahjongCardUniqueId.generate(code, index)

// 从唯一ID获取牌码
MahjongCardUniqueId.getCode(uniqueId)

// 验证唯一ID
MahjongCardUniqueId.validate(uniqueId)

RoomConfigUtils.js - 房间配置工具

功能:房间配置的解析和验证(详见 06-规则配置系统.md


💡 使用最佳实践

1. 日志级别选择

// ✓ 推荐:根据环境设置日志级别
if (process.env.NODE_ENV === 'production') {
  Logger.setLevel('WARN');  // 生产环境只记录警告和错误
} else {
  Logger.setLevel('DEBUG'); // 开发环境记录调试信息
}

// ✓ 推荐:使用合适的日志级别
Logger.trace('Module', '详细跟踪信息');  // 非常详细,仅开发调试用
Logger.debug('Module', '调试信息');       // 开发时使用
Logger.info('Module', '正常运行信息');    // 关键流程节点
Logger.warn('Module', '警告信息');        // 可能的问题
Logger.error('Module', '错误信息');       // 功能异常
Logger.fatal('Module', '致命错误');       // 系统无法继续

// ✗ 不推荐:滥用日志级别
Logger.error('Module', '玩家进入房间');  // 这应该是INFO级别
Logger.info('Module', '系统崩溃');       // 这应该是FATAL级别

2. 日志标签规范

// ✓ 推荐:使用清晰的标签
Logger.info('GameController', '游戏开始');
Logger.debug('MahjongService', '发牌完成');
Logger.warn('OperationManager', '操作超时');

// ✗ 不推荐:标签不清晰
Logger.info('GC', 'start');          // 太简短
Logger.info('游戏', '开始');          // 使用中文不便于过滤

3. 附加数据使用

// ✓ 推荐:提供有用的上下文信息
Logger.error('RpcHandler', 'RPC调用失败', {
  method: 'player_discard',
  playerId: 'player001',
  errorCode: 2001,
  timestamp: Date.now()
});

// ✗ 不推荐:数据过多或无用
Logger.info('Module', '操作', {
  // 包含整个gameState对象太大
  gameState: this.gameState
});

4. 性能考虑

// ✓ 推荐:仅在需要时记录日志
if (Logger.config.currentLevel <= Logger.LOG_LEVELS.DEBUG) {
  var expensiveData = this._calculateExpensiveData();
  Logger.debug('Module', '详细数据', expensiveData);
}

// ✗ 不推荐:总是计算数据
var expensiveData = this._calculateExpensiveData();
Logger.debug('Module', '详细数据', expensiveData);  // DEBUG被禁用时也会计算

5. 错误处理策略

// ✓ 推荐:创建标准化错误
throw ErrorHandler.createError(
  ErrorHandler.ERROR_CODES.GAME_INVALID_ACTION,
  '具体的错误描述',
  { 有用的上下文信息 }
);

// ✓ 推荐:使用安全执行
var result = ErrorHandler.safeExecute(riskyFunction, context, args);
if (!result.success) {
  // 处理错误
}

// ✗ 不推荐:直接抛出字符串
throw '出错了';  // 没有错误码,无法分类

// ✗ 不推荐:忽略错误
try {
  riskyOperation();
} catch (error) {
  // 什么都不做,错误被吞噬
}

6. 日志和错误配合使用

// ✓ 推荐:记录错误日志
try {
  performOperation();
} catch (error) {
  var standardError = ErrorHandler.createError(
    ErrorHandler.ERROR_CODES.GAME_INVALID_ACTION,
    error.message,
    { operation: 'performOperation' }
  );
  
  Logger.error('Module', '操作失败', {
    error: standardError.message,
    code: standardError.code
  });
  
  throw standardError;
}

// ✗ 不推荐:只记录日志或只抛出错误
try {
  performOperation();
} catch (error) {
  Logger.error('Module', error.message);  // 只记录,不抛出
  // 或
  throw error;  // 只抛出,不记录
}

🔍 调试技巧

1. 启用详细日志

// 临时启用TRACE级别查看所有日志
Logger.setLevel('TRACE');

// 执行需要调试的代码
someFunction();

// 恢复原来的级别
Logger.setLevel('INFO');

2. 使用日志分组

// 使用分组整理相关日志
Logger.group('===== 玩家操作处理 =====');
Logger.info('Operation', '验证玩家');
Logger.debug('Operation', '检查游戏状态');
Logger.debug('Operation', '执行操作');
Logger.info('Operation', '操作完成');
Logger.groupEnd();

3. 性能分析

// 使用计时功能分析性能
Logger.time('完整游戏流程');

Logger.time('初始化');
initialize();
Logger.timeEnd('初始化');

Logger.time('发牌');
dealTiles();
Logger.timeEnd('发牌');

Logger.time('游戏进行');
playGame();
Logger.timeEnd('游戏进行');

Logger.timeEnd('完整游戏流程');

4. 查看错误历史

// 获取最近的错误
var errors = ErrorHandler.getStatistics().recent;
Logger.table(errors);

// 分析错误分布
var stats = ErrorHandler.getStatistics();
Logger.info('ErrorStats', '错误统计', {
  total: stats.total,
  byLevel: stats.byLevel,
  topErrors: Object.keys(stats.byCode)
    .sort(function(a, b) {
      return stats.byCode[b] - stats.byCode[a];
    })
    .slice(0, 5)
});

5. 日志过滤

// 只查看特定级别的日志
var errorLogs = Logger.getHistory(null, Logger.LOG_LEVELS.ERROR);
var warnLogs = Logger.getHistory(null, Logger.LOG_LEVELS.WARN);

// 只查看最近的日志
var recent = Logger.getHistory(50);  // 最近50条

// 自定义过滤
var gameLogs = Logger.getHistory().filter(function(log) {
  return log.tag.indexOf('Game') !== -1;
});

📚 完整使用示例

示例1模块初始化

// 模块入口文件

(function() {
  'use strict';
  
  // 配置Logger
  Logger.configure({
    currentLevel: Logger.LOG_LEVELS.DEBUG,
    enableTimestamp: true,
    enableColors: true,
    moduleName: 'JinXianMahjong'
  });
  
  Logger.info('System', '======= 进贤麻将系统启动 =======');
  Logger.info('System', '版本:', '1.0.0');
  Logger.info('System', '环境:', process.env.NODE_ENV || 'development');
  
  // 初始化系统
  try {
    Logger.group('系统初始化');
    
    Logger.time('加载配置');
    var config = loadConfiguration();
    Logger.timeEnd('加载配置');
    Logger.debug('System', '配置加载完成', config);
    
    Logger.time('初始化数据库');
    initializeDatabase();
    Logger.timeEnd('初始化数据库');
    Logger.info('System', '数据库初始化完成');
    
    Logger.time('注册游戏模块');
    registerGameModule();
    Logger.timeEnd('注册游戏模块');
    Logger.info('System', '游戏模块注册完成');
    
    Logger.groupEnd();
    Logger.info('System', '======= 系统启动完成 =======');
    
  } catch (error) {
    var systemError = ErrorHandler.createError(
      ErrorHandler.ERROR_CODES.SYSTEM_INITIALIZATION_FAILED,
      '系统初始化失败: ' + error.message,
      { error: error.stack },
      ErrorHandler.ERROR_LEVELS.FATAL
    );
    
    Logger.fatal('System', '系统启动失败', {
      error: systemError.message,
      code: systemError.code
    });
    
    process.exit(1);
  }
})();

示例2完整的操作流程

// 玩家出牌操作的完整流程

function handlePlayerDiscard(playerId, tileCode) {
  Logger.info('DiscardHandler', '======= 处理玩家出牌 =======', {
    playerId: playerId,
    tileCode: tileCode
  });
  
  try {
    Logger.group('出牌验证');
    
    // 1. 参数验证
    Logger.debug('Validation', '验证参数');
    if (!playerId || !tileCode) {
      throw ErrorHandler.createError(
        ErrorHandler.ERROR_CODES.INTERFACE_INVALID_PARAMS,
        '出牌参数无效',
        { playerId: playerId, tileCode: tileCode }
      );
    }
    
    // 2. 玩家验证
    Logger.debug('Validation', '验证玩家');
    var player = getPlayer(playerId);
    if (!player) {
      throw ErrorHandler.createError(
        ErrorHandler.ERROR_CODES.INTERFACE_PLAYER_NOT_FOUND,
        '玩家不存在',
        { playerId: playerId }
      );
    }
    
    // 3. 游戏状态验证
    Logger.debug('Validation', '验证游戏状态');
    var gameState = getGameState();
    if (gameState.phase !== 'PLAYING') {
      throw ErrorHandler.createError(
        ErrorHandler.ERROR_CODES.GAME_WRONG_PHASE,
        '游戏未在进行中',
        { currentPhase: gameState.phase }
      );
    }
    
    // 4. 回合验证
    Logger.debug('Validation', '验证玩家回合');
    if (!isPlayerTurn(playerId)) {
      throw ErrorHandler.createError(
        ErrorHandler.ERROR_CODES.GAME_INVALID_ACTION,
        '不是该玩家的回合',
        { playerId: playerId, currentPlayer: getCurrentPlayer() }
      );
    }
    
    Logger.groupEnd();
    Logger.info('Validation', '验证通过');
    
    // 5. 执行出牌
    Logger.group('执行出牌');
    Logger.time('出牌操作');
    
    var result = ErrorHandler.safeExecute(
      function() {
        return performDiscard(player, tileCode);
      },
      this,
      []
    );
    
    Logger.timeEnd('出牌操作');
    
    if (!result.success) {
      throw result.error;
    }
    
    Logger.info('Discard', '出牌成功', {
      playerId: playerId,
      tileCode: tileCode,
      discardedTile: result.result
    });
    
    Logger.groupEnd();
    
    // 6. 检查其他玩家的响应
    Logger.group('检查玩家响应');
    var responses = checkPlayerResponses(tileCode);
    Logger.debug('Response', '玩家响应', responses);
    Logger.groupEnd();
    
    // 7. 广播消息
    Logger.debug('Broadcast', '广播出牌消息');
    broadcastDiscard(playerId, tileCode, responses);
    
    Logger.info('DiscardHandler', '======= 出牌处理完成 =======');
    
    return {
      success: true,
      result: {
        playerId: playerId,
        tile: tileCode,
        responses: responses
      }
    };
    
  } catch (error) {
    Logger.error('DiscardHandler', '出牌处理失败', {
      error: error.message,
      code: error.code,
      playerId: playerId,
      tileCode: tileCode
    });
    
    // 记录错误统计
    var stats = ErrorHandler.getStatistics();
    Logger.warn('DiscardHandler', '当前错误统计', {
      total: stats.total,
      recent: stats.recent.length
    });
    
    return {
      success: false,
      error: ErrorHandler.formatErrorMessage(error)
    };
  }
}

🔗 相关文档链接


📝 附录

A. Logger配置选项完整列表

{
  currentLevel: 0-5,              // 当前日志级别
  enableConsole: true/false,      // 启用控制台输出
  enableColors: true/false,       // 启用颜色
  enableTimestamp: true/false,    // 启用时间戳
  enableModuleName: true/false,   // 启用模块名
  enableStackTrace: true/false,   // 启用堆栈跟踪
  timestampFormat: 'ISO'/'LOCAL'/'TIMESTAMP', // 时间戳格式
  moduleName: 'string',           // 模块名
  maxLogHistory: number,          // 最大历史记录数
  flushInterval: number           // 刷新间隔(毫秒)
}

B. 错误代码快速查询

范围 类别 说明
1000-1999 框架接口错误 Export/Import接口相关
2000-2999 游戏逻辑错误 游戏规则和操作相关
3000-3999 数据验证错误 参数和数据格式验证
4000-4999 业务逻辑错误 业务规则和状态相关
5000-5999 网络通信错误 网络连接和通信相关
9000-9999 系统错误 系统级别的严重错误

C. 常用日志模式

// 1. 方法入口/出口模式
function someMethod(param) {
  Logger.debug('Module', 'someMethod 入口', { param: param });
  try {
    // 方法逻辑
    var result = doSomething(param);
    Logger.debug('Module', 'someMethod 出口', { result: result });
    return result;
  } catch (error) {
    Logger.error('Module', 'someMethod 异常', { error: error.message });
    throw error;
  }
}

// 2. 状态变更模式
function changeState(newState) {
  Logger.info('StateManager', '状态变更', {
    from: this.currentState,
    to: newState
  });
  this.currentState = newState;
}

// 3. 性能监控模式
function performanceMonitor(operation) {
  Logger.time(operation.name);
  var result = operation.execute();
  Logger.timeEnd(operation.name);
  return result;
}

文档版本v1.0
最后更新2025年10月15日
维护者:进贤麻将开发团队