Files
autoAiWorkSys/_doc/客户端待开发功能.md
张成 933f1618ca 1
2025-12-25 23:01:21 +08:00

1175 lines
28 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 客户端待开发功能文档
> 本文档说明客户端需要实现的功能所有操作通过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&degree=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<Array>} 职位列表
*/
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<Array>} 职位信息数组
*/
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<boolean>} 是否还有更多内容
*/
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<boolean>} 是否被拦截
*/
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&degree=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<Object>} 投递结果
*/
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<Object>} 刷新结果
*/
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
**维护者**: 开发团队