From 933f1618cabf90dad32fd7382a54cbb08c0b1ec5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=88=90?= Date: Thu, 25 Dec 2025 23:01:21 +0800 Subject: [PATCH] 1 --- _doc/MQTT指令列表.md | 968 +++++++++++++++++++++++++++++++ _doc/客户端待开发功能.md | 1174 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 2142 insertions(+) create mode 100644 _doc/MQTT指令列表.md create mode 100644 _doc/客户端待开发功能.md diff --git a/_doc/MQTT指令列表.md b/_doc/MQTT指令列表.md new file mode 100644 index 0000000..28597de --- /dev/null +++ b/_doc/MQTT指令列表.md @@ -0,0 +1,968 @@ +# 自动找工作系统 - MQTT指令列表 + +> 版本: v1.0 | 更新日期: 2025-12-25 + +## 文档说明 + +本文档定义了服务端通过MQTT向客户端下发的所有指令格式和规范。所有操作都通过任务和指令的方式异步执行。 + +--- + +## 一、MQTT通信架构 + +### 1.1 通信流程 + +``` +┌──────────────┐ ┌──────────────┐ +│ 服务端 │ │ 客户端 │ +│ (Node.js) │ │ (设备端) │ +└──────┬───────┘ └──────┬───────┘ + │ │ + │ ① 创建任务(task_status表) │ + │ ② 生成指令(task_commands表) │ + │ │ + │ ③ MQTT Publish │ + │ Topic: {sn_code}/command │ + │ ─────────────────────────────> │ + │ │ + │ ④ 执行指令 │ + │ ⑤ 生成结果 │ + │ │ + │ ⑥ MQTT Publish │ + │ Topic: response │ + │ <───────────────────────────── │ + │ │ + │ ⑦ 更新指令状态(task_commands) │ + │ ⑧ 更新任务状态(task_status) │ + │ │ +``` + +### 1.2 MQTT配置 + +- **Broker地址**: `mqtt://192.144.167.231:1883` +- **订阅主题**: + - `heartbeat` - 设备心跳信息 + - `response` - 设备响应信息 +- **发布主题**: + - `{sn_code}/command` - 向指定设备发送指令 + +### 1.3 消息格式 + +**服务端 → 客户端 (指令)** +```json +{ + "commandId": "uuid", + "taskId": "uuid", + "platform": "boss", + "action": "search_jobs", + "data": { + "keyword": "全栈工程师", + "city": "101020100", + "page": 1 + } +} +``` + +**客户端 → 服务端 (响应)** +```json +{ + "commandId": "uuid", + "taskId": "uuid", + "code": 200, + "message": "执行成功", + "data": { + // 返回数据 + } +} +``` + +**客户端 → 服务端 (心跳)** +```json +{ + "sn_code": "device001", + "platform": "boss", + "timestamp": 1672531200000, + "status": "online", + "version": "1.0.0" +} +``` + +--- + +## 二、已实现指令列表 + +### 2.1 用户登录指令 + +#### get_login_qr_code - 获取登录二维码 + +**指令格式** +```json +{ + "action": "get_login_qr_code", + "platform": "boss", + "data": {} +} +``` + +**返回格式** +```json +{ + "code": 200, + "message": "二维码获取成功", + "data": { + "qrCode": "https://example.com/qrcode.png", + "qr_code_url": "https://example.com/qrcode.png", + "expire_time": 300 + } +} +``` + +**说明**: 获取登录二维码,用户扫码登录后客户端需要保存cookies/token + +--- + +#### get_user_info - 获取用户信息 + +**指令格式** +```json +{ + "action": "get_user_info", + "platform": "boss", + "data": {} +} +``` + +**返回格式** +```json +{ + "code": 200, + "message": "获取成功", + "data": { + "userId": "123456", + "userName": "张三", + "phone": "138****5678", + "isLoggedIn": true + } +} +``` + +**说明**: 获取当前登录用户的基本信息,验证登录状态 + +--- + +### 2.2 简历管理指令 + +#### get_online_resume - 获取在线简历 + +**指令格式** +```json +{ + "action": "get_online_resume", + "platform": "boss", + "data": {} +} +``` + +**返回格式** +```json +{ + "code": 200, + "message": "获取成功", + "data": { + "baseInfo": { + "name": "张三", + "gender": 1, + "age": 28, + "account": "138****5678", + "emailBlur": "zhang***@qq.com", + "workYears": 5, + "workYearDesc": "5年", + "degreeCategory": "本科" + }, + "expectList": [{ + "positionName": "全栈工程师", + "locationName": "上海", + "salaryDesc": "20-30K", + "industryDesc": "互联网" + }], + "workExpList": [{ + "companyName": "XX科技公司", + "positionName": "高级前端工程师", + "startDate": "2020-01", + "endDate": "2023-12", + "workContent": "负责前端架构设计和开发..." + }], + "projectExpList": [{ + "name": "电商平台项目", + "roleName": "技术负责人", + "startDate": "2022-01", + "endDate": "2023-06", + "projectDesc": "项目描述...", + "performance": "项目成果..." + }], + "educationExpList": [{ + "school": "XX大学", + "major": "计算机科学与技术", + "degreeName": "本科", + "endYear": 2018 + }], + "userDesc": "熟悉Vue、React、Node.js等技术栈...", + "certificationList": [] + } +} +``` + +**说明**: 获取用户在招聘平台上的完整简历信息 + +--- + +### 2.3 岗位搜索指令 + +#### search_jobs - 搜索岗位 (已实现但需完善) + +**指令格式** +```json +{ + "action": "search_jobs", + "platform": "boss", + "data": { + "keyword": "全栈工程师", + "city": "101020100", + "page": 1 + } +} +``` + +**返回格式** +```json +{ + "code": 200, + "message": "搜索成功", + "data": { + "total": 150, + "page": 1, + "jobList": [{ + "jobId": "job123456", + "jobTitle": "全栈工程师", + "companyName": "XX科技公司", + "companySize": "100-499人", + "salary": "20-30K", + "location": "上海·浦东新区", + "experience": "3-5年", + "education": "本科", + "jobRequirements": "1. 熟悉Vue/React...", + "jobDescription": "岗位职责...", + "bossName": "张经理", + "bossTitle": "技术总监" + }] + } +} +``` + +**说明**: 当前实现基础,需要扩展支持更多搜索条件 + +--- + +#### get_job_list - 获取岗位列表 + +**指令格式** +```json +{ + "action": "get_job_list", + "platform": "boss", + "data": { + "page": 1, + "pageSize": 20 + } +} +``` + +**返回格式** +```json +{ + "code": 200, + "message": "获取成功", + "data": { + "total": 50, + "page": 1, + "jobList": [ + // 同 search_jobs 的 jobList 格式 + ] + } +} +``` + +**说明**: 获取推荐岗位列表 + +--- + +### 2.4 投递管理指令 + +#### apply_job - 投递岗位 (基础实现) + +**指令格式** +```json +{ + "action": "apply_job", + "platform": "boss", + "data": { + "jobId": "job123456", + "expectSalary": "20-30K" + } +} +``` + +**返回格式** +```json +{ + "code": 200, + "message": "投递成功", + "data": { + "applyId": "apply123456", + "jobId": "job123456", + "applyTime": "2025-12-25 10:30:00", + "status": "success" + } +} +``` + +**错误码** +- `400` - 参数错误 +- `403` - 已投递过该岗位 +- `429` - 投递次数达到上限 +- `500` - 投递失败 + +**说明**: 向指定岗位投递简历 + +--- + +### 2.5 聊天管理指令 + +#### get_chat_list - 获取聊天列表 + +**指令格式** +```json +{ + "action": "get_chat_list", + "platform": "boss", + "data": { + "page": 1, + "pageSize": 20 + } +} +``` + +**返回格式** +```json +{ + "code": 200, + "message": "获取成功", + "data": { + "total": 15, + "chatList": [{ + "conversationId": "conv123456", + "jobId": "job123456", + "jobTitle": "全栈工程师", + "companyName": "XX科技", + "bossName": "张经理", + "lastMessage": "您好,请问...", + "lastMessageTime": "2025-12-25 10:30:00", + "unreadCount": 2, + "hasInterview": false + }] + } +} +``` + +**说明**: 获取与HR的聊天会话列表 + +--- + +#### send_chat_message - 发送聊天消息 + +**指令格式** +```json +{ + "action": "send_chat_message", + "platform": "boss", + "data": { + "conversationId": "conv123456", + "jobId": "job123456", + "content": "您好,我对这个岗位很感兴趣..." + } +} +``` + +**返回格式** +```json +{ + "code": 200, + "message": "发送成功", + "data": { + "messageId": "msg123456", + "sendTime": "2025-12-25 10:30:00" + } +} +``` + +**说明**: 向HR发送聊天消息 + +--- + +### 2.6 测试和调试指令 + +#### open_bot_detection - 打开测试页 + +**指令格式** +```json +{ + "action": "open_bot_detection", + "platform": "boss", + "data": {} +} +``` + +**返回格式** +```json +{ + "code": 200, + "message": "测试页已打开", + "data": {} +} +``` + +**说明**: 打开测试页面,用于调试 + +--- + +## 三、待开发指令列表 + +### 3.1 搜索投递增强指令 (优先级: HIGH) + +#### search_jobs_enhanced - 增强搜索岗位 ⭐⭐⭐⭐⭐ + +**指令格式** +```json +{ + "action": "search_jobs_enhanced", + "platform": "boss", + "data": { + "keyword": "全栈工程师", + "city": "101020100", + "page": 1, + "pageSize": 20, + + // 新增搜索条件 + "experience": "3", + "degree": "203", + "salary": "406", + "scale": "303", + "stage": "807", + "position": "100109", + + // 滚动加载方式 + "scrollLoadType": "auto", + "maxScrollPages": 5 + } +} +``` + +**参数说明** +| 参数 | 说明 | 示例值 | +|------|------|--------| +| keyword | 搜索关键词 | "全栈工程师" | +| city | 城市代码 | "101020100" (上海) | +| page | 页码 | 1 | +| pageSize | 每页数量 | 20 | +| experience | 工作经验 | "1"=1年以下, "3"=1-3年, "4"=3-5年, "5"=5-10年, "6"=10年以上 | +| degree | 学历要求 | "202"=不限, "203"=大专, "204"=本科, "205"=硕士, "206"=博士 | +| salary | 薪资范围 | "402"=3-5K, "403"=5-10K, "404"=10-15K, "405"=15-20K, "406"=20-30K, "407"=30-50K, "408"=50K以上 | +| scale | 公司规模 | "302"=0-20人, "303"=20-99人, "304"=100-499人, "305"=500-999人, "306"=1000人以上 | +| stage | 融资阶段 | "801"=未融资, "802"=天使轮, "803"=A轮, "804"=B轮, "805"=C轮, "806"=D轮及以上, "807"=已上市, "808"=不需要融资 | +| position | 职位类型 | "100109"=全栈, "100110"=前端, "100111"=后端, "100112"=移动端 | +| scrollLoadType | 加载方式 | "auto"=自动滚动, "manual"=手动翻页 | +| maxScrollPages | 最大滚动页数 | 5 | + +**返回格式** +```json +{ + "code": 200, + "message": "搜索成功", + "data": { + "total": 150, + "page": 1, + "hasMore": true, + "jobList": [{ + "jobId": "job123456", + "jobTitle": "全栈工程师", + "companyName": "XX科技公司", + "companySize": "100-499人", + "companyIndustry": "互联网", + "companyStage": "已上市", + "salary": "20-30K", + "salaryMonth": "14薪", + "location": "上海·浦东新区", + "longitude": 121.5273, + "latitude": 31.2172, + "experience": "3-5年", + "education": "本科", + "skills": ["Vue", "React", "Node.js"], + "jobRequirements": "1. 熟悉Vue/React...", + "jobDescription": "岗位职责...", + "welfare": ["五险一金", "带薪年假", "弹性工作"], + "bossName": "张经理", + "bossTitle": "技术总监", + "bossActiveStatus": "刚刚活跃", + "publishTime": "2025-12-25", + "viewCount": 150, + "applyCount": 30, + "isOutsourcing": false, + "jobLink": "https://www.zhipin.com/job_detail/xxx" + }] + } +} +``` + +**说明**: +- 支持Boss直聘完整的搜索筛选条件 +- 支持自动滚动加载更多岗位 +- 返回更详细的岗位信息 + +--- + +#### search_by_url - 通过URL搜索岗位 ⭐⭐⭐⭐⭐ + +**指令格式** +```json +{ + "action": "search_by_url", + "platform": "boss", + "data": { + "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", + "scrollLoadType": "auto", + "maxScrollPages": 5 + } +} +``` + +**返回格式** +```json +{ + "code": 200, + "message": "搜索成功", + "data": { + // 同 search_jobs_enhanced 返回格式 + } +} +``` + +**说明**: +- 直接使用Boss直聘的搜索URL +- 自动解析URL参数 +- 支持所有筛选条件 + +--- + +### 3.2 批量投递指令 (优先级: HIGH) + +#### batch_apply_jobs - 批量投递岗位 ⭐⭐⭐⭐⭐ + +**指令格式** +```json +{ + "action": "batch_apply_jobs", + "platform": "boss", + "data": { + "jobIds": ["job001", "job002", "job003"], + "expectSalary": "20-30K", + "applyInterval": 30, + "maxApplyCount": 10 + } +} +``` + +**参数说明** +| 参数 | 说明 | 示例值 | +|------|------|--------| +| jobIds | 岗位ID数组 | ["job001", "job002"] | +| expectSalary | 期望薪资 | "20-30K" | +| applyInterval | 投递间隔(秒) | 30 | +| maxApplyCount | 最大投递数量 | 10 | + +**返回格式** +```json +{ + "code": 200, + "message": "批量投递完成", + "data": { + "total": 10, + "success": 8, + "failed": 2, + "results": [{ + "jobId": "job001", + "status": "success", + "applyId": "apply001", + "message": "投递成功" + }, { + "jobId": "job002", + "status": "failed", + "message": "已投递过该岗位" + }] + } +} +``` + +**说明**: +- 批量投递多个岗位 +- 控制投递间隔避免被限制 +- 返回每个岗位的投递结果 + +--- + +### 3.3 简历刷新指令 (优先级: HIGH) + +#### refresh_resume - 刷新简历 ⭐⭐⭐⭐ + +**指令格式** +```json +{ + "action": "refresh_resume", + "platform": "boss", + "data": {} +} +``` + +**返回格式** +```json +{ + "code": 200, + "message": "简历刷新成功", + "data": { + "refreshTime": "2025-12-25 10:30:00", + "nextRefreshTime": "2025-12-25 12:30:00" + } +} +``` + +**说明**: +- 刷新简历提升排名 +- 每2小时可刷新一次 + +--- + +### 3.4 账号保活指令 (优先级: HIGH) + +#### auto_active - 自动活跃账号 ⭐⭐⭐⭐ + +**指令格式** +```json +{ + "action": "auto_active", + "platform": "boss", + "data": { + "actionType": "random", + "actions": ["browse_jobs", "view_company", "search_keyword", "update_visibility"] + } +} +``` + +**参数说明** +| 参数 | 说明 | 可选值 | +|------|------|--------| +| actionType | 动作类型 | "random"=随机, "sequence"=顺序 | +| actions | 动作列表 | ["browse_jobs", "view_company", "search_keyword", "update_visibility"] | + +**动作说明** +- `browse_jobs` - 浏览岗位(随机点击5-10个岗位) +- `view_company` - 查看公司主页 +- `search_keyword` - 搜索关键词(随机关键词) +- `update_visibility` - 修改简历可见性 + +**返回格式** +```json +{ + "code": 200, + "message": "活跃操作完成", + "data": { + "executedActions": ["browse_jobs", "view_company"], + "duration": 120, + "timestamp": "2025-12-25 10:30:00" + } +} +``` + +**说明**: +- 模拟真实用户行为 +- 随机时间间隔 +- 避免账号被标记为机器人 + +--- + +### 3.5 聊天增强指令 (优先级: MEDIUM) + +#### get_chat_detail - 获取聊天详情 ⭐⭐⭐ + +**指令格式** +```json +{ + "action": "get_chat_detail", + "platform": "boss", + "data": { + "conversationId": "conv123456", + "page": 1, + "pageSize": 50 + } +} +``` + +**返回格式** +```json +{ + "code": 200, + "message": "获取成功", + "data": { + "conversationId": "conv123456", + "jobId": "job123456", + "messages": [{ + "messageId": "msg001", + "senderId": "boss123", + "senderType": "boss", + "content": "您好,请问什么时候方便面试?", + "sendTime": "2025-12-25 10:30:00", + "isRead": true, + "messageType": "text", + "isInterviewInvitation": true + }] + } +} +``` + +**说明**: 获取完整的聊天历史记录 + +--- + +#### send_greeting - 发送打招呼 ⭐⭐⭐ + +**指令格式** +```json +{ + "action": "send_greeting", + "platform": "boss", + "data": { + "jobId": "job123456", + "content": "您好,我对这个岗位很感兴趣,期待能有机会详聊。" + } +} +``` + +**返回格式** +```json +{ + "code": 200, + "message": "打招呼成功", + "data": { + "conversationId": "conv123456", + "messageId": "msg001", + "sendTime": "2025-12-25 10:30:00" + } +} +``` + +**说明**: 主动向HR发起沟通 + +--- + +### 3.6 数据采集指令 (优先级: MEDIUM) + +#### get_job_detail - 获取岗位详情 ⭐⭐⭐ + +**指令格式** +```json +{ + "action": "get_job_detail", + "platform": "boss", + "data": { + "jobId": "job123456" + } +} +``` + +**返回格式** +```json +{ + "code": 200, + "message": "获取成功", + "data": { + "jobId": "job123456", + // 完整的岗位详情(同search_jobs_enhanced中的jobList项) + "companyDetail": { + "companyId": "company123", + "companyName": "XX科技公司", + "companyLogo": "https://...", + "companySize": "100-499人", + "companyIndustry": "互联网", + "companyStage": "已上市", + "companyAddress": "上海市浦东新区...", + "companyDesc": "公司介绍..." + } + } +} +``` + +**说明**: 获取岗位的完整详情信息 + +--- + +#### get_company_info - 获取公司信息 ⭐⭐⭐ + +**指令格式** +```json +{ + "action": "get_company_info", + "platform": "boss", + "data": { + "companyId": "company123" + } +} +``` + +**返回格式** +```json +{ + "code": 200, + "message": "获取成功", + "data": { + "companyId": "company123", + "companyName": "XX科技公司", + "companyLogo": "https://...", + "companySize": "100-499人", + "companyIndustry": "互联网", + "companyStage": "已上市", + "companyAddress": "上海市浦东新区...", + "companyDesc": "公司介绍...", + "companyBenefit": ["五险一金", "带薪年假"], + "companyPhotos": ["https://...", "https://..."], + "jobCount": 50, + "isVerified": true + } +} +``` + +**说明**: 获取公司的详细信息 + +--- + +## 四、指令执行规范 + +### 4.1 指令生命周期 + +``` +1. 创建 (pending) + ↓ +2. 下发 (sent) + ↓ +3. 执行中 (executing) + ↓ +4. 完成 (completed) / 失败 (failed) / 超时 (timeout) +``` + +### 4.2 超时设置 + +| 指令类型 | 超时时间 | 重试次数 | +|---------|----------|----------| +| 登录类指令 | 60秒 | 1次 | +| 简历获取 | 30秒 | 2次 | +| 岗位搜索 | 60秒 | 2次 | +| 岗位投递 | 30秒 | 1次 | +| 聊天消息 | 30秒 | 2次 | +| 保活操作 | 120秒 | 0次 | + +### 4.3 错误码规范 + +| 错误码 | 说明 | 处理方式 | +|--------|------|----------| +| 200 | 成功 | - | +| 400 | 参数错误 | 不重试 | +| 401 | 未登录 | 触发重新登录 | +| 403 | 无权限/已操作 | 不重试 | +| 429 | 请求过于频繁 | 延迟后重试 | +| 500 | 服务器错误 | 重试 | +| 503 | 服务不可用 | 延迟后重试 | +| 600 | 网络超时 | 重试 | +| 700 | 客户端错误 | 记录日志,不重试 | + +### 4.4 重试策略 + +- **指数退避**: `delay = min(1000 * 2^(retryCount-1), 30000ms)` +- **最大重试次数**: 根据指令类型决定(见4.2表格) +- **可重试错误**: 429, 500, 503, 600 +- **不可重试错误**: 400, 401, 403, 700 + +--- + +## 五、客户端实现要求 + +### 5.1 MQTT客户端 + +- **连接保持**: 断线自动重连 +- **心跳间隔**: 10秒 +- **订阅主题**: `{sn_code}/command` +- **发布主题**: `response`, `heartbeat` + +### 5.2 指令处理 + +1. **接收指令** + - 解析JSON格式 + - 验证必需字段 + - 记录指令日志 + +2. **执行指令** + - 根据action分发到对应处理器 + - 更新执行状态 + - 捕获异常错误 + +3. **返回响应** + - 统一响应格式 + - 包含commandId用于追踪 + - 返回详细的执行结果 + +### 5.3 异常处理 + +- **网络异常**: 自动重试 +- **登录过期**: 通知服务端重新登录 +- **页面加载失败**: 刷新页面重试 +- **元素定位失败**: 记录截图,返回错误 + +### 5.4 日志记录 + +- **请求日志**: 记录所有接收到的指令 +- **响应日志**: 记录所有返回的响应 +- **错误日志**: 记录所有异常和错误 +- **操作日志**: 记录关键操作步骤 + +--- + +## 六、开发优先级 + +### P0 - 立即开发 (投递核心功能) + +1. ✅ `search_jobs_enhanced` - 增强搜索 +2. ✅ `search_by_url` - URL搜索 +3. ✅ `batch_apply_jobs` - 批量投递 +4. ✅ `refresh_resume` - 简历刷新 + +### P1 - 短期开发 (保活和聊天) + +5. ✅ `auto_active` - 账号保活 +6. ✅ `send_greeting` - 发送打招呼 +7. ✅ `get_chat_detail` - 聊天详情 + +### P2 - 中期开发 (数据采集) + +8. ⭐ `get_job_detail` - 岗位详情 +9. ⭐ `get_company_info` - 公司信息 + +--- + +**文档维护**: 开发团队 +**最后更新**: 2025-12-25 diff --git a/_doc/客户端待开发功能.md b/_doc/客户端待开发功能.md new file mode 100644 index 0000000..fb14628 --- /dev/null +++ b/_doc/客户端待开发功能.md @@ -0,0 +1,1174 @@ +# 客户端待开发功能文档 + +> 本文档说明客户端需要实现的功能,所有操作通过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 +**维护者**: 开发团队