# 客户端待开发功能文档 > 本文档说明客户端需要实现的功能,所有操作通过MQTT接收服务端指令执行 --- ## 一、客户端架构概览 ### 1.1 技术栈 - **自动化框架**: Puppeteer / Playwright (推荐Playwright,支持更好的反爬) - **运行环境**: Node.js 18+ - **通信协议**: MQTT (mqtt://192.144.167.231:1883) - **消息格式**: JSON - **浏览器**: Chromium (无头模式/有头模式可切换) ### 1.2 核心模块 ``` client/ ├── src/ │ ├── mqtt/ # MQTT通信模块 │ │ ├── client.js # MQTT客户端 │ │ └── handler.js # 消息处理器 │ ├── platforms/ # 平台适配器 │ │ ├── boss/ # Boss直聘 │ │ │ ├── login.js # 登录模块 │ │ │ ├── search.js # 搜索模块 ⚠️ 待开发 │ │ │ ├── apply.js # 投递模块 ⚠️ 待完善 │ │ │ ├── resume.js # 简历模块 ⚠️ 待开发 │ │ │ ├── chat.js # 聊天模块 ⚠️ 待完善 │ │ │ └── active.js # 保活模块 ⚠️ 待开发 │ │ └── zhilian/ # 智联招聘 (同上) │ ├── utils/ # 工具函数 │ │ ├── browser.js # 浏览器管理 │ │ ├── antibot.js # 反爬虫处理 │ │ └── logger.js # 日志记录 │ └── index.js # 入口文件 └── config/ └── default.json # 配置文件 ``` ### 1.3 工作流程 ``` 服务端 → MQTT → 客户端接收 → 路由到平台模块 → 执行操作 → 返回结果 → MQTT → 服务端 ``` --- ## 二、高优先级功能 (P0) > 与投递直接相关的核心功能 ### 2.1 增强搜索功能 ⚠️ 待开发 #### 功能说明 实现Boss直聘完整的搜索功能,支持所有筛选项和URL解析 #### MQTT指令 - `search_jobs_enhanced` - 带完整筛选参数的搜索 - `search_by_url` - 直接解析URL搜索 #### 目标URL示例 ``` https://www.zhipin.com/web/geek/jobs?city=101020100&query=%E5%85%A8%E6%A0%88%E5%B7%A5%E7%A8%8B%E5%B8%88&experience=103°ree=203&salary=404 ``` #### 实现要点 **1. URL参数解析** ```javascript // platforms/boss/search.js /** * 解析Boss直聘搜索URL * @param {string} url - 搜索URL * @returns {Object} 解析后的参数对象 */ function parseSearchUrl(url) { const urlObj = new URL(url); const params = {}; // 城市代码 if (urlObj.searchParams.get('city')) { params.city = urlObj.searchParams.get('city'); } // 搜索关键词 if (urlObj.searchParams.get('query')) { params.query = decodeURIComponent(urlObj.searchParams.get('query')); } // 工作经验 if (urlObj.searchParams.get('experience')) { params.experience = urlObj.searchParams.get('experience'); } // 学历要求 if (urlObj.searchParams.get('degree')) { params.degree = urlObj.searchParams.get('degree'); } // 薪资范围 if (urlObj.searchParams.get('salary')) { params.salary = urlObj.searchParams.get('salary'); } // 公司规模 if (urlObj.searchParams.get('scale')) { params.scale = urlObj.searchParams.get('scale'); } // 融资阶段 if (urlObj.searchParams.get('stage')) { params.stage = urlObj.searchParams.get('stage'); } // 职位类型 if (urlObj.searchParams.get('position')) { params.position = urlObj.searchParams.get('position'); } // 行业领域 if (urlObj.searchParams.get('industry')) { params.industry = urlObj.searchParams.get('industry'); } return params; } /** * 构建搜索URL * @param {Object} params - 搜索参数 * @returns {string} 完整的搜索URL */ function buildSearchUrl(params) { const baseUrl = 'https://www.zhipin.com/web/geek/jobs'; const searchParams = new URLSearchParams(); // 必填参数 if (params.city) searchParams.set('city', params.city); if (params.query) searchParams.set('query', params.query); // 可选筛选参数 if (params.experience) searchParams.set('experience', params.experience); if (params.degree) searchParams.set('degree', params.degree); if (params.salary) searchParams.set('salary', params.salary); if (params.scale) searchParams.set('scale', params.scale); if (params.stage) searchParams.set('stage', params.stage); if (params.position) searchParams.set('position', params.position); if (params.industry) searchParams.set('industry', params.industry); return `${baseUrl}?${searchParams.toString()}`; } ``` **2. 搜索执行** ```javascript /** * 执行增强搜索 * @param {Page} page - Playwright页面对象 * @param {Object} params - 搜索参数 * @returns {Promise} 职位列表 */ async function searchJobsEnhanced(page, params) { const url = buildSearchUrl(params); // 访问搜索页面 await page.goto(url, { waitUntil: 'networkidle' }); // 等待职位列表加载 await page.waitForSelector('.job-card-wrapper', { timeout: 10000 }); // 反爬虫检测 const isBlocked = await checkBotDetection(page); if (isBlocked) { throw new Error('检测到反爬虫,需要人工介入'); } const jobs = []; let hasMore = true; let scrollCount = 0; const maxScrolls = params.maxResults ? Math.ceil(params.maxResults / 30) : 3; while (hasMore && scrollCount < maxScrolls) { // 提取当前页面职位 const pageJobs = await extractJobsFromPage(page); jobs.push(...pageJobs); // 滚动加载更多 hasMore = await scrollToLoadMore(page); scrollCount++; // 随机延迟,模拟人类行为 await randomDelay(1000, 3000); } return jobs; } /** * 从页面提取职位信息 * @param {Page} page - Playwright页面对象 * @returns {Promise} 职位信息数组 */ async function extractJobsFromPage(page) { return await page.$$eval('.job-card-wrapper', (cards) => { return cards.map(card => { const jobName = card.querySelector('.job-name')?.textContent.trim(); const jobArea = card.querySelector('.job-area')?.textContent.trim(); const salary = card.querySelector('.salary')?.textContent.trim(); const companyName = card.querySelector('.company-name a')?.textContent.trim(); const companyTag = Array.from(card.querySelectorAll('.company-tag-list li')) .map(li => li.textContent.trim()); const bossInfo = card.querySelector('.info-public')?.textContent.trim(); const jobLink = card.querySelector('.job-card-left')?.href; const jobId = jobLink?.match(/job_detail\/(\w+)\.html/)?.[1]; // 提取标签(经验、学历) const tags = Array.from(card.querySelectorAll('.tag-list li')) .map(li => li.textContent.trim()); return { jobId, jobName, jobArea, salary, companyName, companyTags: companyTag, experience: tags[0] || '', degree: tags[1] || '', bossInfo, jobLink, tags }; }); }); } /** * 滚动加载更多职位 * @param {Page} page - Playwright页面对象 * @returns {Promise} 是否还有更多内容 */ async function scrollToLoadMore(page) { // 滚动到页面底部 await page.evaluate(() => { window.scrollTo(0, document.body.scrollHeight); }); // 等待新内容加载 await page.waitForTimeout(2000); // 检查是否有"加载更多"按钮或已到底部 const loadMoreBtn = await page.$('.load-more'); if (loadMoreBtn) { await loadMoreBtn.click(); await page.waitForTimeout(1500); return true; } // 检查是否显示"没有更多了" const noMore = await page.$('.no-more-tips'); return !noMore; } ``` **3. 反爬虫处理** ```javascript // utils/antibot.js /** * 检测是否被反爬虫拦截 * @param {Page} page - Playwright页面对象 * @returns {Promise} 是否被拦截 */ async function checkBotDetection(page) { const indicators = [ () => page.$('.captcha-container'), // 验证码 () => page.$('.security-check'), // 安全检查 () => page.content().then(c => c.includes('频繁访问')), () => page.content().then(c => c.includes('请稍后再试')) ]; for (const check of indicators) { if (await check()) { return true; } } return false; } /** * 随机延迟 * @param {number} min - 最小毫秒 * @param {number} max - 最大毫秒 */ async function randomDelay(min, max) { const delay = Math.floor(Math.random() * (max - min + 1)) + min; await new Promise(resolve => setTimeout(resolve, delay)); } /** * 模拟人类鼠标移动 * @param {Page} page - Playwright页面对象 */ async function simulateHumanBehavior(page) { // 随机移动鼠标 const randomX = Math.floor(Math.random() * 800) + 100; const randomY = Math.floor(Math.random() * 600) + 100; await page.mouse.move(randomX, randomY); // 随机滚动 await page.evaluate(() => { const scrollAmount = Math.floor(Math.random() * 300) + 100; window.scrollBy(0, scrollAmount); }); await randomDelay(500, 1500); } ``` **4. MQTT指令处理器** ```javascript // mqtt/handler.js - 增强搜索指令处理 async function handleSearchJobsEnhanced(payload) { const { platform, params, taskId } = payload; try { // 获取平台对应的浏览器页面 const page = await browserManager.getPage(platform); // 确保已登录 await ensureLoggedIn(page, platform); // 执行搜索 const searchModule = require(`../platforms/${platform}/search.js`); const jobs = await searchModule.searchJobsEnhanced(page, params); // 返回结果 return { code: 0, message: '搜索成功', data: { total: jobs.length, jobs: jobs }, taskId }; } catch (error) { console.error('搜索失败:', error); return { code: -1, message: error.message, taskId }; } } async function handleSearchByUrl(payload) { const { platform, url, taskId } = payload; try { const searchModule = require(`../platforms/${platform}/search.js`); // 解析URL const params = searchModule.parseSearchUrl(url); // 执行搜索 const page = await browserManager.getPage(platform); await ensureLoggedIn(page, platform); const jobs = await searchModule.searchJobsEnhanced(page, params); return { code: 0, message: '搜索成功', data: { total: jobs.length, jobs: jobs, params: params // 返回解析的参数 }, taskId }; } catch (error) { console.error('URL搜索失败:', error); return { code: -1, message: error.message, taskId }; } } ``` #### 参数映射表 **工作经验 (experience)** - `103`: 应届生 - `104`: 1年以内 - `105`: 1-3年 - `106`: 3-5年 - `107`: 5-10年 - `108`: 10年以上 **学历要求 (degree)** - `203`: 大专 - `204`: 本科 - `205`: 硕士 - `206`: 博士 **薪资范围 (salary)** - `402`: 3K以下 - `403`: 3-5K - `404`: 5-10K - `405`: 10-15K - `406`: 15-25K - `407`: 25-35K - `408`: 35-50K - `409`: 50K以上 **公司规模 (scale)** - `301`: 0-20人 - `302`: 20-99人 - `303`: 100-499人 - `304`: 500-999人 - `305`: 1000-9999人 - `306`: 10000人以上 **融资阶段 (stage)** - `801`: 未融资 - `802`: 天使轮 - `803`: A轮 - `804`: B轮 - `805`: C轮 - `806`: D轮及以上 - `807`: 已上市 - `808`: 不需要融资 #### 测试用例 ```javascript // 测试用例1: 参数搜索 { action: 'search_jobs_enhanced', platform: 'boss', params: { city: '101020100', // 上海 query: '全栈工程师', experience: '106', // 3-5年 degree: '204', // 本科 salary: '406', // 15-25K scale: '303', // 100-499人 maxResults: 100 } } // 测试用例2: URL搜索 { action: 'search_by_url', platform: 'boss', url: 'https://www.zhipin.com/web/geek/jobs?city=101020100&query=%E5%85%A8%E6%A0%88%E5%B7%A5%E7%A8%8B%E5%B8%88&experience=106°ree=204&salary=406' } ``` --- ### 2.2 批量投递功能 ⚠️ 待完善 #### 功能说明 支持批量投递职位,带智能过滤和频率控制 #### MQTT指令 - `batch_apply_jobs` - 批量投递 #### 实现要点 ```javascript // platforms/boss/apply.js /** * 批量投递职位 * @param {Page} page - Playwright页面对象 * @param {Array} jobIds - 职位ID列表 * @param {Object} options - 投递选项 * @returns {Promise} 投递结果 */ async function batchApplyJobs(page, jobIds, options = {}) { const { maxPerHour = 20, // 每小时最多投递数 delayMin = 10000, // 最小间隔(ms) delayMax = 30000, // 最大间隔(ms) skipApplied = true, // 跳过已投递 greetingMessage = '' // 打招呼消息 } = options; const results = { total: jobIds.length, success: 0, failed: 0, skipped: 0, details: [] }; let appliedThisHour = 0; let hourStartTime = Date.now(); for (const jobId of jobIds) { // 频率控制 if (appliedThisHour >= maxPerHour) { const elapsed = Date.now() - hourStartTime; if (elapsed < 3600000) { const waitTime = 3600000 - elapsed; console.log(`已达每小时投递上限,等待 ${waitTime/1000} 秒`); await new Promise(resolve => setTimeout(resolve, waitTime)); appliedThisHour = 0; hourStartTime = Date.now(); } } try { // 检查是否已投递 if (skipApplied) { const applied = await checkIfApplied(page, jobId); if (applied) { results.skipped++; results.details.push({ jobId, status: 'skipped', reason: '已投递' }); continue; } } // 投递职位 await applyJob(page, jobId, greetingMessage); results.success++; appliedThisHour++; results.details.push({ jobId, status: 'success' }); // 随机延迟 const delay = Math.floor(Math.random() * (delayMax - delayMin + 1)) + delayMin; await new Promise(resolve => setTimeout(resolve, delay)); } catch (error) { results.failed++; results.details.push({ jobId, status: 'failed', reason: error.message }); } } return results; } /** * 检查职位是否已投递 */ async function checkIfApplied(page, jobId) { await page.goto(`https://www.zhipin.com/job_detail/${jobId}.html`); await page.waitForTimeout(1000); // 查找"继续沟通"或"已沟通"按钮 const appliedBtn = await page.$('.btn-applied, .btn-chatted'); return !!appliedBtn; } /** * 投递单个职位 */ async function applyJob(page, jobId, greetingMessage) { await page.goto(`https://www.zhipin.com/job_detail/${jobId}.html`); await page.waitForSelector('.btn-startchat', { timeout: 5000 }); // 点击"立即沟通" await page.click('.btn-startchat'); // 如果有打招呼消息 if (greetingMessage) { await page.waitForSelector('.chat-input', { timeout: 3000 }); await page.fill('.chat-input', greetingMessage); await page.click('.send-btn'); } await page.waitForTimeout(1000); } ``` --- ### 2.3 简历刷新功能 ⚠️ 待开发 #### 功能说明 定时刷新简历,提升曝光度 #### MQTT指令 - `refresh_resume` - 刷新简历 #### 实现要点 ```javascript // platforms/boss/resume.js /** * 刷新简历 * @param {Page} page - Playwright页面对象 * @returns {Promise} 刷新结果 */ async function refreshResume(page) { try { // 进入简历管理页面 await page.goto('https://www.zhipin.com/web/geek/profile', { waitUntil: 'networkidle' }); // 查找刷新按钮 const refreshBtn = await page.$('.refresh-resume-btn, .update-resume-btn'); if (!refreshBtn) { throw new Error('未找到刷新按钮'); } // 检查是否可刷新(可能有时间限制) const disabled = await refreshBtn.getAttribute('disabled'); if (disabled) { // 获取下次可刷新时间 const nextRefreshTime = await page.$eval('.next-refresh-time', el => el.textContent); throw new Error(`暂时无法刷新,下次可刷新时间: ${nextRefreshTime}`); } // 点击刷新 await refreshBtn.click(); await page.waitForTimeout(2000); // 确认刷新 const confirmBtn = await page.$('.confirm-refresh'); if (confirmBtn) { await confirmBtn.click(); await page.waitForTimeout(1000); } return { success: true, message: '简历刷新成功', timestamp: new Date().toISOString() }; } catch (error) { throw new Error(`简历刷新失败: ${error.message}`); } } /** * 获取简历刷新状态 */ async function getResumeRefreshStatus(page) { await page.goto('https://www.zhipin.com/web/geek/profile'); const status = await page.evaluate(() => { const refreshBtn = document.querySelector('.refresh-resume-btn'); const isDisabled = refreshBtn?.hasAttribute('disabled'); const nextTime = document.querySelector('.next-refresh-time')?.textContent; const lastRefreshTime = document.querySelector('.last-refresh-time')?.textContent; return { canRefresh: !isDisabled, nextRefreshTime: nextTime, lastRefreshTime: lastRefreshTime }; }); return status; } ``` --- ## 三、中优先级功能 (P1) ### 3.1 账号保活功能 ⚠️ 待开发 #### 功能说明 模拟真实用户行为,防止账号被标记为机器人 #### MQTT指令 - `auto_active` - 执行保活操作 #### 实现要点 ```javascript // platforms/boss/active.js /** * 执行保活操作 * @param {Page} page - Playwright页面对象 * @param {Object} options - 保活选项 */ async function performActiveActions(page, options = {}) { const { duration = 300000, // 持续时间(ms) 默认5分钟 actions = ['browse', 'search', 'view_company', 'scroll'] } = options; const startTime = Date.now(); const actionsPerformed = []; while (Date.now() - startTime < duration) { // 随机选择一个行为 const action = actions[Math.floor(Math.random() * actions.length)]; try { switch (action) { case 'browse': await browseJobList(page); break; case 'search': await performRandomSearch(page); break; case 'view_company': await viewRandomCompany(page); break; case 'scroll': await randomScroll(page); break; } actionsPerformed.push({ action, timestamp: new Date().toISOString() }); // 随机延迟 await randomDelay(5000, 15000); } catch (error) { console.error(`保活操作失败: ${action}`, error); } } return { success: true, duration: Date.now() - startTime, actionsPerformed: actionsPerformed.length, details: actionsPerformed }; } /** * 浏览职位列表 */ async function browseJobList(page) { await page.goto('https://www.zhipin.com/web/geek/jobs', { waitUntil: 'networkidle' }); // 随机滚动 for (let i = 0; i < 3; i++) { await page.evaluate(() => { window.scrollBy(0, Math.random() * 500 + 200); }); await randomDelay(1000, 3000); } } /** * 执行随机搜索 */ async function performRandomSearch(page) { const keywords = ['前端', '后端', 'Java', 'Python', '产品经理', '运营']; const keyword = keywords[Math.floor(Math.random() * keywords.length)]; await page.goto(`https://www.zhipin.com/web/geek/jobs?query=${encodeURIComponent(keyword)}`); await page.waitForTimeout(2000); // 随机点击一个职位查看 const jobCards = await page.$$('.job-card-wrapper'); if (jobCards.length > 0) { const randomIndex = Math.floor(Math.random() * Math.min(jobCards.length, 5)); await jobCards[randomIndex].click(); await page.waitForTimeout(3000); } } /** * 查看随机公司 */ async function viewRandomCompany(page) { await page.goto('https://www.zhipin.com/web/geek/jobs'); await page.waitForTimeout(2000); const companyLinks = await page.$$('.company-name a'); if (companyLinks.length > 0) { const randomIndex = Math.floor(Math.random() * Math.min(companyLinks.length, 5)); await companyLinks[randomIndex].click(); await page.waitForTimeout(5000); } } /** * 随机滚动 */ async function randomScroll(page) { const scrollTimes = Math.floor(Math.random() * 5) + 2; for (let i = 0; i < scrollTimes; i++) { await page.evaluate(() => { const direction = Math.random() > 0.5 ? 1 : -1; const distance = Math.random() * 300 + 100; window.scrollBy(0, direction * distance); }); await randomDelay(800, 2000); } } ``` --- ### 3.2 智能聊天功能 ⚠️ 待完善 #### 功能说明 自动回复HR消息,提高响应率 #### MQTT指令 - `send_greeting` - 发送打招呼消息 - `auto_reply_chat` - 自动回复聊天 #### 实现要点 ```javascript // platforms/boss/chat.js /** * 发送打招呼消息 * @param {Page} page - Playwright页面对象 * @param {string} bossId - HRID * @param {string} message - 消息内容 */ async function sendGreeting(page, bossId, message) { // 进入聊天页面 await page.goto(`https://www.zhipin.com/web/geek/chat?bosId=${bossId}`); await page.waitForSelector('.chat-input', { timeout: 5000 }); // 输入消息 await page.fill('.chat-input', message); await randomDelay(500, 1500); // 发送 await page.click('.send-btn'); await page.waitForTimeout(1000); return { success: true, bossId, message, timestamp: new Date().toISOString() }; } /** * 自动回复聊天 * @param {Page} page - Playwright页面对象 * @param {Object} options - 回复选项 */ async function autoReplyChat(page, options = {}) { const { checkInterval = 60000, // 检查间隔(ms) replyDelay = 30000, // 回复延迟(ms) 模拟思考时间 maxReplies = 10 // 最多回复次数 } = options; let repliedCount = 0; // 获取未读消息 await page.goto('https://www.zhipin.com/web/geek/chat'); await page.waitForTimeout(2000); const unreadChats = await page.$$('.chat-item.unread'); for (const chat of unreadChats) { if (repliedCount >= maxReplies) break; try { // 点击进入聊天 await chat.click(); await page.waitForTimeout(1000); // 获取最后一条消息 const lastMessage = await page.$eval('.message-list .message-item:last-child .message-text', el => el.textContent.trim() ); // 调用服务端AI生成回复 // 这里需要通过MQTT发送消息给服务端,服务端调用AI生成回复后返回 // 为了简化,这里暂时使用预设回复 const reply = generateAutoReply(lastMessage); // 等待一段时间,模拟真人思考 await randomDelay(replyDelay * 0.8, replyDelay * 1.2); // 发送回复 await page.fill('.chat-input', reply); await page.click('.send-btn'); await page.waitForTimeout(1000); repliedCount++; } catch (error) { console.error('自动回复失败:', error); } } return { success: true, totalUnread: unreadChats.length, replied: repliedCount }; } /** * 生成自动回复(简化版,实际应调用服务端AI) */ function generateAutoReply(message) { const lowerMessage = message.toLowerCase(); if (lowerMessage.includes('面试') || lowerMessage.includes('interview')) { return '好的,感谢您的面试邀请!请问具体的面试时间和地点是什么?'; } if (lowerMessage.includes('简历') || lowerMessage.includes('resume')) { return '您好,我已经投递了简历,请查收。如有任何问题,欢迎随时沟通!'; } if (lowerMessage.includes('薪资') || lowerMessage.includes('salary')) { return '关于薪资,我的期望是根据岗位要求和市场情况来定的,具体可以详细沟通。'; } // 默认回复 return '收到,感谢您的消息!'; } ``` --- ## 四、低优先级功能 (P2) ### 4.1 数据监控上报 ```javascript /** * 上报性能数据 */ async function reportMetrics(metrics) { const reportData = { action: 'report_metrics', metrics: { ...metrics, timestamp: new Date().toISOString(), deviceInfo: { platform: process.platform, memory: process.memoryUsage(), uptime: process.uptime() } } }; await mqttClient.publish(`device/${sn_code}/metrics`, JSON.stringify(reportData)); } ``` ### 4.2 截图上报 ```javascript /** * 截图并上报 */ async function captureAndReport(page, reason) { const screenshot = await page.screenshot({ fullPage: false }); const base64 = screenshot.toString('base64'); await mqttClient.publish(`device/${sn_code}/screenshot`, JSON.stringify({ reason, image: base64, timestamp: new Date().toISOString() })); } ``` --- ## 五、开发规范 ### 5.1 错误处理 所有函数必须包含try-catch,并返回标准格式: ```javascript { code: 0, // 0成功,-1失败 message: '操作成功', data: {}, // 返回数据 taskId: 'xxx', // 任务ID timestamp: 'ISO时间' } ``` ### 5.2 日志记录 ```javascript const logger = require('../utils/logger'); logger.info('操作成功', { action: 'search_jobs', jobCount: 50 }); logger.error('操作失败', { action: 'apply_job', error: error.message }); logger.warn('检测到异常', { type: 'bot_detection' }); ``` ### 5.3 配置管理 ```json // config/default.json { "mqtt": { "broker": "mqtt://192.144.167.231:1883", "username": "admin", "password": "public" }, "browser": { "headless": false, "slowMo": 50, "defaultTimeout": 30000 }, "antibot": { "randomDelay": { "min": 1000, "max": 3000 }, "humanBehavior": true }, "limits": { "maxApplyPerHour": 20, "maxApplyPerDay": 100, "maxSearchPerHour": 50 } } ``` --- ## 六、测试计划 ### 6.1 单元测试 为每个模块编写单元测试: ```javascript // tests/platforms/boss/search.test.js describe('Boss搜索模块', () => { test('URL解析正确', () => { const url = 'https://www.zhipin.com/web/geek/jobs?city=101020100&query=全栈'; const params = parseSearchUrl(url); expect(params.city).toBe('101020100'); expect(params.query).toBe('全栈'); }); test('搜索执行成功', async () => { const jobs = await searchJobsEnhanced(page, { city: '101020100', query: '前端工程师' }); expect(jobs.length).toBeGreaterThan(0); }); }); ``` ### 6.2 集成测试 测试完整的MQTT指令流程: ```javascript // tests/integration/search.test.js describe('搜索功能集成测试', () => { test('通过MQTT执行搜索', async () => { const response = await mqttClient.publishAndWait('test_device', { action: 'search_jobs_enhanced', platform: 'boss', params: { city: '101020100', query: '测试工程师' } }); expect(response.code).toBe(0); expect(response.data.jobs).toBeDefined(); }); }); ``` --- ## 七、部署和运维 ### 7.1 环境要求 - Node.js 18+ - Chromium/Chrome 浏览器 - 充足的内存(建议2GB+) - 稳定的网络连接 ### 7.2 启动命令 ```bash # 开发模式 npm run dev # 生产模式 npm run start # 后台运行(Linux) pm2 start src/index.js --name "job-client" ``` ### 7.3 监控指标 - 任务执行成功率 - 平均响应时间 - 反爬虫触发次数 - 账号状态(正常/异常) --- ## 八、开发时间估算 | 功能模块 | 优先级 | 预计工时 | 备注 | |---------|--------|---------|------| | 增强搜索功能 | P0 | 3天 | 含URL解析和参数映射 | | 批量投递功能 | P0 | 2天 | 含频率控制 | | 简历刷新功能 | P0 | 1天 | 较简单 | | 账号保活功能 | P1 | 2天 | 需模拟多种行为 | | 智能聊天功能 | P1 | 3天 | 需AI对接 | | 数据监控上报 | P2 | 1天 | 辅助功能 | | **总计** | - | **12天** | 约2.5周 | --- ## 九、风险和注意事项 ### 9.1 反爬虫风险 - 操作频率过高可能触发验证码 - 建议设置合理的延迟和频率限制 - 模拟真实用户行为(鼠标移动、随机滚动) ### 9.2 账号安全 - 避免同时操作过多账号 - 登录后保持Cookie有效期 - 定期执行保活操作 ### 9.3 性能问题 - 浏览器实例占用较多内存 - 建议实现浏览器实例池管理 - 长时间运行需要定期重启 --- ## 十、参考资料 - [Playwright 官方文档](https://playwright.dev/) - [MQTT.js 文档](https://github.com/mqttjs/MQTT.js) - [Boss直聘网站分析](https://www.zhipin.com/) - [反爬虫最佳实践](https://github.com/topics/anti-bot) --- **文档版本**: v1.0 **最后更新**: 2025-12-25 **维护者**: 开发团队