zhilian-auto-hi
Version:
智联招聘自动打招呼工具 - 自动化招聘流程的命令行工具
487 lines (486 loc) • 19.7 kB
JavaScript
;
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
};