UNPKG

zhilian-auto-hi

Version:

智联招聘自动打招呼工具 - 自动化招聘流程的命令行工具

487 lines (486 loc) 19.7 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.DelayUtils = exports.RandomDelayGenerator = void 0; exports.validateDelayConfig = validateDelayConfig; const index_1 = require("../logger/index"); const ora_1 = __importDefault(require("ora")); /** * 验证延迟配置参数 * * 验证策略说明: * - 基准时间必须为正数:确保有实际的延迟效果 * - 变化范围限制在0-1:防止过度随机化,保持可控性 * - 最小延迟至少100ms:避免过快操作被检测为机器行为 * - 最大延迟不超过10秒:防止用户体验过差,避免超时 * - 边界逻辑检查:确保配置的数学逻辑正确 */ function validateDelayConfig(config) { if (config.base <= 0) { throw new Error('Base delay must be positive'); } if (config.variance < 0 || config.variance > 1) { throw new Error('Variance must be between 0 and 1'); } if (config.min < 100) { throw new Error('Minimum delay must be at least 100ms'); } if (config.max > 10000) { throw new Error('Maximum delay must not exceed 10 seconds'); } if (config.min >= config.max) { throw new Error('Minimum delay must be less than maximum'); } } /** * 随机延迟生成器类 */ class RandomDelayGenerator { /** * 根据配置生成随机延迟时间 * * 算法设计理念: * 1. 以基准时间为中心,按变化范围生成正态分布的随机时间 * 2. 使用 Math.random() 生成均匀分布,模拟人类行为的随机性 * 3. 通过边界限制确保生成的时间在合理范围内 * 4. 四舍五入到整数毫秒,避免浮点数精度问题 * * 选择这种算法的原因: * - 简单高效,计算开销小 * - 分布相对均匀,避免明显的模式 * - 边界控制严格,确保稳定性 * - 易于理解和调试 */ static generateDelay(config) { validateDelayConfig(config); // 计算变化范围:基准时间 ± (基准时间 × 变化百分比) const varianceAmount = config.base * config.variance; const minVariance = config.base - varianceAmount; const maxVariance = config.base + varianceAmount; // 在变化范围内生成随机延迟时间 // 使用均匀分布模拟人类操作时间的随机性 const randomDelay = Math.random() * (maxVariance - minVariance) + minVariance; // 应用边界限制并四舍五入到整数毫秒 // 这确保了延迟时间既随机又在可接受的范围内 return Math.max(config.min, Math.min(config.max, Math.round(randomDelay))); } /** * 获取预设配置 * * 预设设计原则: * 1. 基于真实用户行为数据和经验值设计 * 2. 考虑不同操作类型的特点和需求 * 3. 平衡自然性、效率和用户体验 * 4. 提供足够的随机性以避免检测 * 5. 保持配置的可维护性和可理解性 */ static getPreset(preset) { const presets = { // 短延迟:用于快速交互,如按钮点击、输入框聚焦 // 优化v2:基于测试结果进一步优化,提高检测规避效果 short: { base: 280, // 微调基准时间,更符合人类快速反应 variance: 0.65, // 增加变化范围,提高随机性 min: 120, // 略微提高最小值,避免过快被检测 max: 480 // 优化最大值,保持响应性 }, // 中延迟:用于一般操作,如页面元素加载、表单提交 // 优化v2:基于性能测试结果调整,平衡效率和自然性 medium: { base: 850, // 微调基准时间,更符合实际使用场景 variance: 0.45, // 调整变化范围,减少极端值 min: 450, // 提高最小值,确保基本等待时间 max: 1400 // 优化最大值,避免过长等待 }, // 长延迟:用于复杂操作,如页面完全加载、大数据处理 // 优化v2:基于统计测试结果优化分布 long: { base: 2600, // 调整基准时间,更符合现代网络环境 variance: 0.4, // 增加变化范围,模拟网络波动 min: 1600, // 降低最小值,适应高速网络 max: 4000 // 调整最大值,避免超时风险 }, // 页面加载延迟:专门用于页面加载等待 // 优化v2:基于端到端测试结果调整 pageLoad: { base: 2100, // 基于实际页面加载时间统计优化 variance: 0.5, // 增加变化范围,更好模拟网络条件差异 min: 1100, // 降低最小值,适应快速加载场景 max: 3300 // 调整最大值,平衡等待时间和用户体验 }, // 交互延迟:用于用户交互模拟,如鼠标移动、滚动 // 优化v2:基于人类行为研究数据优化 interaction: { base: 420, // 微调基准时间,更符合用户操作节奏 variance: 0.75, // 增加变化范围,提高行为随机性 min: 140, // 调整最小值,允许快速连续操作 max: 950 // 优化最大值,避免操作过慢 } }; return presets[preset]; } /** * 执行人性化延迟 */ static async humanLikeDelay(page, config) { const delayMs = this.generateDelay(config); await page.waitForTimeout(delayMs); } } exports.RandomDelayGenerator = RandomDelayGenerator; /** * 延迟工具类 - 提供便捷的延迟函数和日志记录功能 */ class DelayUtils { /** * 设置调试模式 */ static setDebugMode(enabled) { this.debugMode = enabled; } /** * 设置日志记录开关 */ static setLogging(enabled) { this.enableLogging = enabled; } /** * 设置进度指示器开关 */ static setProgressIndicator(enabled) { this.enableProgressIndicator = enabled; } /** * 获取延迟统计信息 */ static getDelayStats() { return { ...this.delayStats }; } /** * 重置延迟统计信息 */ static resetDelayStats() { this.delayStats = { totalDelays: 0, totalDelayTime: 0, averageDelay: 0, shortDelays: 0, mediumDelays: 0, longDelays: 0 }; } /** * 记录延迟统计信息 */ static recordDelayStats(delayMs) { this.delayStats.totalDelays++; this.delayStats.totalDelayTime += delayMs; this.delayStats.averageDelay = this.delayStats.totalDelayTime / this.delayStats.totalDelays; if (delayMs < 1000) { this.delayStats.shortDelays++; } else if (delayMs < 2000) { this.delayStats.mediumDelays++; } else { this.delayStats.longDelays++; } } /** * 记录延迟执行信息 */ static logDelayExecution(delayMs, preset, config) { if (!this.enableLogging || !index_1.logger) return; const presetInfo = preset ? ` [${preset}]` : ''; const configInfo = config && this.debugMode ? ` (基准: ${config.base}ms, 范围: ±${(config.variance * 100).toFixed(0)}%)` : ''; index_1.logger.info(`执行延迟${presetInfo}: ${delayMs}ms${configInfo}`, '⏱️'); // 记录统计信息 this.recordDelayStats(delayMs); } /** * 使用预设配置的随机延迟 * * 策略选择说明: * - 使用预设配置简化使用,提高一致性 * - 自动选择是否显示进度,优化用户体验 * - 集成日志记录和性能监控,便于调试 * - 支持长延迟的特殊处理,避免用户等待焦虑 * * @param page Playwright页面对象 - 用于执行实际的延迟操作 * @param preset 预设配置名称 - 根据操作类型选择合适的延迟策略 */ static async randomDelay(page, preset) { const config = RandomDelayGenerator.getPreset(preset); const delayMs = RandomDelayGenerator.generateDelay(config); // 记录延迟执行信息 this.logDelayExecution(delayMs, preset, config); // 对于长延迟显示进度 if (delayMs > 2000 && this.enableProgressIndicator) { await this.longDelayWithProgress(page, delayMs, preset); } else { const startTime = Date.now(); await page.waitForTimeout(delayMs); const actualDelay = Date.now() - startTime; // 监控延迟执行性能 this.monitorDelayPerformance(delayMs, actualDelay); } } /** * 使用自定义配置的延迟 * @param page Playwright页面对象 * @param config 自定义延迟配置 */ static async customDelay(page, config) { const delayMs = RandomDelayGenerator.generateDelay(config); // 记录延迟执行信息 this.logDelayExecution(delayMs, undefined, config); // 对于长延迟显示进度 if (delayMs > 2000 && this.enableProgressIndicator) { await this.longDelayWithProgress(page, delayMs, 'custom'); } else { const startTime = Date.now(); await page.waitForTimeout(delayMs); const actualDelay = Date.now() - startTime; // 监控延迟执行性能 this.monitorDelayPerformance(delayMs, actualDelay); } } /** * 带进度显示的长延迟 * * 长延迟处理策略: * 1. 分段显示进度,减少用户等待焦虑 * 2. 限制进度点数量,避免过于频繁的更新 * 3. 显示剩余时间和已用时间,提供清晰的反馈 * 4. 监控实际执行时间,确保延迟准确性 * 5. 优雅的开始和结束提示,提升用户体验 * * 选择这种策略的原因: * - 长延迟容易让用户以为程序卡死 * - 进度显示增加用户信心和耐心 * - 分段执行便于中断和错误处理 * - 时间监控有助于性能优化 * * @param page Playwright页面对象 * @param delayMs 延迟时间(毫秒) * @param type 延迟类型(用于日志显示) */ static async longDelayWithProgress(page, delayMs, type = 'long') { const seconds = Math.ceil(delayMs / 1000); const startTime = Date.now(); let spinner = null; if (this.enableLogging) { spinner = (0, ora_1.default)(`${type === 'custom' ? '自定义' : type}长延迟等待中... (${delayMs}ms)`).start(); } // 分段显示进度 const intervals = Math.min(seconds, 10); // 最多显示10个进度点 const intervalMs = delayMs / intervals; for (let i = 1; i <= intervals; i++) { await page.waitForTimeout(intervalMs); if (this.enableLogging && this.enableProgressIndicator && spinner) { const progress = Math.round((i / intervals) * 100); const remaining = Math.round(delayMs - i * intervalMs); const elapsed = Date.now() - startTime; spinner.text = `${type === 'custom' ? '自定义' : type}长延迟等待中... ${progress}% ` + `(剩余: ${remaining}ms, 已用: ${elapsed}ms)`; } } const actualDelay = Date.now() - startTime; if (this.enableLogging && spinner) { spinner.succeed(`延迟完成 (预期: ${delayMs}ms, 实际: ${actualDelay}ms)`); } // 如果实际延迟与预期差异较大,记录调试信息 if (this.debugMode && Math.abs(actualDelay - delayMs) > 100) { index_1.logger.info(`长延迟时间差异: ${actualDelay - delayMs}ms`, '📊'); } } /** * 简单的随机延迟(不需要page对象) * @param baseMs 基准时间(毫秒) * @param variancePercent 变化百分比(0-100) */ static async simpleRandomDelay(baseMs, variancePercent = 30) { if (baseMs <= 0) { throw new Error('Base delay must be positive'); } if (variancePercent < 0 || variancePercent > 100) { throw new Error('Variance percent must be between 0 and 100'); } const variance = variancePercent / 100; const config = { base: baseMs, variance: variance, min: Math.max(100, Math.round(baseMs * (1 - variance))), max: Math.min(10000, Math.round(baseMs * (1 + variance))) }; const delayMs = RandomDelayGenerator.generateDelay(config); // 记录延迟执行信息 this.logDelayExecution(delayMs, 'simple', config); const startTime = Date.now(); // 对于长延迟显示倒计时 if (delayMs > 2000 && this.enableProgressIndicator && this.enableLogging) { await this.simpleDelayWithCountdown(delayMs); } else { // 使用Promise和setTimeout实现延迟 await new Promise(resolve => setTimeout(resolve, delayMs)); } const actualDelay = Date.now() - startTime; if (this.debugMode && Math.abs(actualDelay - delayMs) > 50) { index_1.logger.info(`简单延迟时间差异: ${actualDelay - delayMs}ms`, '📊'); } } /** * 简单延迟的倒计时显示 * @param delayMs 延迟时间(毫秒) */ static async simpleDelayWithCountdown(delayMs) { const seconds = Math.ceil(delayMs / 1000); const spinner = (0, ora_1.default)(`简单延迟倒计时... ${seconds}秒`).start(); for (let i = seconds; i > 0; i--) { spinner.text = `简单延迟倒计时... ${i}秒`; await new Promise(resolve => setTimeout(resolve, 1000)); } spinner.succeed(`简单延迟完成 (${delayMs}ms)`); } /** * 显示延迟统计信息 */ static showDelayStats() { if (!this.enableLogging) return; const stats = this.getDelayStats(); if (stats.totalDelays === 0) { index_1.logger.info('暂无延迟统计信息', '📊'); return; } index_1.logger.separator('延迟统计信息'); index_1.logger.info(`总延迟次数: ${stats.totalDelays}`, '🔢'); index_1.logger.info(`总延迟时间: ${stats.totalDelayTime}ms (${(stats.totalDelayTime / 1000).toFixed(1)}秒)`, '⏱️'); index_1.logger.info(`平均延迟时间: ${stats.averageDelay.toFixed(1)}ms`, '📊'); index_1.logger.info(`短延迟(<1s): ${stats.shortDelays}次`, '⚡'); index_1.logger.info(`中延迟(1-2s): ${stats.mediumDelays}次`, '⏳'); index_1.logger.info(`长延迟(>2s): ${stats.longDelays}次`, '🐌'); const efficiency = stats.totalDelayTime > 0 ? ((stats.totalDelays * 1000) / stats.totalDelayTime * 100).toFixed(1) : '0'; index_1.logger.info(`延迟效率: ${efficiency}% (理想值越高越好)`, '🎯'); } /** * 获取所有可用的预设配置 */ static getAvailablePresets() { return ['short', 'medium', 'long', 'pageLoad', 'interaction']; } /** * 获取预设配置的详细信息 * @param preset 预设名称 */ static getPresetInfo(preset) { return RandomDelayGenerator.getPreset(preset); } /** * 验证延迟配置是否有效 * @param config 延迟配置 */ static validateConfig(config) { try { validateDelayConfig(config); return true; } catch (error) { if (this.debugMode) { index_1.logger.error(`延迟配置验证失败: ${error instanceof Error ? error.message : String(error)}`); } return false; } } /** * 监控延迟执行性能 * @param delayMs 预期延迟时间 * @param actualMs 实际延迟时间 */ static monitorDelayPerformance(delayMs, actualMs) { if (!this.debugMode || !index_1.logger) return; const difference = actualMs - delayMs; const percentageDiff = Math.abs(difference / delayMs * 100); if (percentageDiff > 10) { // 如果差异超过10% const status = difference > 0 ? '超时' : '提前'; index_1.logger.warning(`延迟性能异常: ${status} ${Math.abs(difference)}ms (${percentageDiff.toFixed(1)}%)`, '⚠️'); } else if (percentageDiff > 5) { // 如果差异超过5% index_1.logger.info(`延迟时间偏差: ${difference}ms (${percentageDiff.toFixed(1)}%)`, '📊'); } } /** * 获取延迟配置的可读描述 * @param config 延迟配置 */ static getConfigDescription(config) { const variancePercent = (config.variance * 100).toFixed(0); return `基准${config.base}ms, 变化±${variancePercent}%, 范围${config.min}-${config.max}ms`; } /** * 批量执行延迟(用于测试) * @param preset 预设名称 * @param count 执行次数 * @param page Playwright页面对象 */ static async batchDelay(preset, count, page) { if (count <= 0) { throw new Error('Batch count must be positive'); } index_1.logger.info(`开始批量延迟测试: ${preset} x ${count}次`, '🧪'); const startTime = Date.now(); for (let i = 1; i <= count; i++) { if (this.enableLogging) { index_1.logger.info(`批量延迟 ${i}/${count}`, '🔄'); } await this.randomDelay(page, preset); } const totalTime = Date.now() - startTime; index_1.logger.success(`批量延迟完成: ${count}次, 总耗时: ${totalTime}ms`, '✅'); if (this.debugMode) { this.showDelayStats(); } } /** * 设置延迟监控配置 * @param options 监控选项 */ static configureMonitoring(options) { if (options.enableLogging !== undefined) { this.setLogging(options.enableLogging); } if (options.enableProgressIndicator !== undefined) { this.setProgressIndicator(options.enableProgressIndicator); } if (options.debugMode !== undefined) { this.setDebugMode(options.debugMode); } if (this.enableLogging && index_1.logger) { index_1.logger.info('延迟监控配置已更新', '⚙️'); index_1.logger.info(`日志记录: ${this.enableLogging ? '开启' : '关闭'}`, '📝'); index_1.logger.info(`进度指示器: ${this.enableProgressIndicator ? '开启' : '关闭'}`, '📊'); index_1.logger.info(`调试模式: ${this.debugMode ? '开启' : '关闭'}`, '🐛'); } } } exports.DelayUtils = DelayUtils; DelayUtils.debugMode = false; DelayUtils.enableLogging = true; DelayUtils.enableProgressIndicator = true; DelayUtils.delayStats = { totalDelays: 0, totalDelayTime: 0, averageDelay: 0, shortDelays: 0, mediumDelays: 0, longDelays: 0 };