diff --git a/api/controller_front/user.js b/api/controller_front/user.js index 910bec2..90144c4 100644 --- a/api/controller_front/user.js +++ b/api/controller_front/user.js @@ -471,18 +471,33 @@ module.exports = { const { sn_code } = body; if (!sn_code) return ctx.fail('请提供设备SN码'); - const { pla_account } = await Framework.getModels(); + const { pla_account, resume_info } = await Framework.getModels(); const user = await pla_account.findOne({ where: { sn_code } }); if (!user) return ctx.fail('用户不存在'); const u = user.toJSON ? user.toJSON() : user; - return ctx.success({ + const result = { deliver_config: u.deliver_config || null, chat_strategy: u.chat_strategy || null, active_actions: u.active_actions || null, auto_chat: u.auto_chat != null ? !!u.auto_chat : false, auto_active: u.auto_active != null ? !!u.auto_active : false + }; + + const platform = u.platform_type || 'boss'; + const resume = await resume_info.findOne({ + where: { sn_code, platform, isActive: true }, + order: [['last_modify_time', 'DESC']] }); + if (resume) { + const r = resume.toJSON ? resume.toJSON() : resume; + result.job_listings = Array.isArray(r.job_listings) ? r.job_listings : []; + result.deliver_tab_label = r.deliver_tab_label != null ? String(r.deliver_tab_label) : ''; + } else { + result.job_listings = []; + result.deliver_tab_label = ''; + } + return ctx.success(result); } catch (error) { console.error('[获取账号配置失败]', error); return ctx.fail('获取账号配置失败'); @@ -495,10 +510,10 @@ module.exports = { 'POST /user/account-config/save': async (ctx) => { try { const body = ctx.getBody(); - const { sn_code, deliver_config, chat_strategy, active_actions } = body; + const { sn_code, deliver_config, chat_strategy, active_actions, deliver_tab_label, job_listings } = body; if (!sn_code) return ctx.fail('请提供设备SN码'); - const { pla_account } = await Framework.getModels(); + const { pla_account, resume_info } = await Framework.getModels(); const user = await pla_account.findOne({ where: { sn_code } }); if (!user) return ctx.fail('用户不存在'); @@ -529,9 +544,37 @@ module.exports = { updateData.active_actions = active_actions; if (active_actions.auto_active !== undefined) updateData.auto_active = active_actions.auto_active ? 1 : 0; } - if (Object.keys(updateData).length === 0) return ctx.success({ message: '无更新' }); + if (deliver_tab_label !== undefined || job_listings !== undefined) { + const platform = user.platform_type || 'boss'; + const account_id = user.account_id != null ? String(user.account_id) : (user.id != null ? String(user.id) : ''); + let resume = await resume_info.findOne({ + where: { sn_code, platform }, + order: [['last_modify_time', 'DESC']] + }); + const resumePayload = {}; + if (deliver_tab_label !== undefined) resumePayload.deliver_tab_label = deliver_tab_label; + if (job_listings !== undefined) resumePayload.job_listings = Array.isArray(job_listings) ? job_listings : []; + if (resume) { + await resume_info.update(resumePayload, { where: { id: resume.id } }); + } else { + await resume_info.create({ + sn_code, + account_id: account_id || '', + platform, + resumeId: '', + deliver_tab_label: resumePayload.deliver_tab_label != null ? resumePayload.deliver_tab_label : '', + job_listings: resumePayload.job_listings || [], + isActive: true + }); + } + } - await pla_account.update(updateData, { where: { id: user.id } }); + if (Object.keys(updateData).length === 0 && deliver_tab_label === undefined && job_listings === undefined) { + return ctx.success({ message: '无更新' }); + } + if (Object.keys(updateData).length > 0) { + await pla_account.update(updateData, { where: { id: user.id } }); + } return ctx.success({ message: '配置保存成功' }); } catch (error) { console.error('[保存账号配置失败]', error); diff --git a/api/middleware/schedule/handlers/deliverHandler.js b/api/middleware/schedule/handlers/deliverHandler.js index 0030ac2..58eada0 100644 --- a/api/middleware/schedule/handlers/deliverHandler.js +++ b/api/middleware/schedule/handlers/deliverHandler.js @@ -85,8 +85,9 @@ class DeliverHandler extends BaseHandler { // 5. 获取职位类型配置 const jobTypeConfig = await this.getJobTypeConfig(accountConfig.job_type_id); - // 6. 搜索职位列表 - await this.searchJobs(sn_code, platform, keyword || accountConfig.keyword, pageCount, task.id); + // 6. 搜索职位列表(从 resume_info 取 deliver_tab_label 作为 tabLabel 下发给 get_job_list 切换期望 tab) + const tabLabel = resume.deliver_tab_label || ''; + await this.searchJobs(sn_code, platform, keyword || accountConfig.keyword, pageCount, task.id, tabLabel); // 7. 从数据库获取待投递职位 const pendingJobs = await this.getPendingJobs(sn_code, platform, actualMaxCount * 3); @@ -238,17 +239,22 @@ class DeliverHandler extends BaseHandler { /** * 搜索职位列表 + * @param {string} tabLabel - 投递用期望标签文案,对应 resume_info.deliver_tab_label,get_job_list 会按此选择 tab */ - async searchJobs(sn_code, platform, keyword, pageCount, taskId) { + async searchJobs(sn_code, platform, keyword, pageCount, taskId, tabLabel = '') { + const params = { + sn_code, + keyword, + platform, + pageCount + }; + if (tabLabel != null && String(tabLabel).trim() !== '') { + params.tabLabel = String(tabLabel).trim(); + } const getJobListCommand = { command_type: 'get_job_list', command_name: 'get_job_list', - command_params: JSON.stringify({ - sn_code, - keyword, - platform, - pageCount - }), + command_params: JSON.stringify(params), priority: config.getTaskPriority('search_jobs') || 5 }; @@ -337,18 +343,22 @@ class DeliverHandler extends BaseHandler { */ async filterAndScoreJobs(jobs, resume, accountConfig, jobTypeConfig, filterConfig, recentCompanies) { const scored = []; + const jobDesc = (j) => `${j.companyName || '?'} / ${j.jobTitle || '?'}`; + + console.log(`[自动投递-过滤] 开始过滤与评分,待处理职位数: ${jobs.length}`); for (const job of jobs) { // 1. 过滤近期已投递的公司 if (job.companyName && recentCompanies.has(job.companyName)) { - console.log(`[自动投递] 跳过已投递公司: ${job.companyName}`); + console.log(`[自动投递-过滤] 步骤1-已投递公司 剔除: ${jobDesc(job)}`); continue; } // 2. 使用 jobFilterEngine 过滤和评分 const filtered = await jobFilterEngine.filterJobs([job], filterConfig, resume); if (filtered.length === 0) { - continue; // 不符合过滤条件 + console.log(`[自动投递-过滤] 步骤2-jobFilterEngine 剔除: ${jobDesc(job)} (不满足过滤条件)`); + continue; } // 3. 使用原有的评分系统(job_filter_service)计算详细分数 @@ -371,21 +381,26 @@ class DeliverHandler extends BaseHandler { const finalScore = scoreResult.totalScore + keywordBonus.score; // 5. 只保留评分 >= 60 的职位 - if (finalScore >= 60) { - scored.push({ - ...job, - matchScore: finalScore, - scoreDetails: { - ...scoreResult.scores, - keywordBonus: keywordBonus.score - } - }); + if (finalScore < 60) { + console.log(`[自动投递-过滤] 步骤5-评分不足(>=60) 剔除: ${jobDesc(job)} | 总分=${finalScore.toFixed(1)} (基础=${scoreResult.totalScore?.toFixed(1)}, 关键词奖励=${keywordBonus.score})`); + continue; } + + scored.push({ + ...job, + matchScore: finalScore, + scoreDetails: { + ...scoreResult.scores, + keywordBonus: keywordBonus.score + } + }); + console.log(`[自动投递-过滤] 通过: ${jobDesc(job)} | 总分=${finalScore.toFixed(1)}`); } // 按评分降序排序 scored.sort((a, b) => b.matchScore - a.matchScore); + console.log(`[自动投递-过滤] 结束: 原始=${jobs.length}, 通过=${scored.length}, 剔除=${jobs.length - scored.length}`); return scored; } diff --git a/api/middleware/schedule/handlers/searchHandler.js b/api/middleware/schedule/handlers/searchHandler.js index 85d1ab9..8c362e7 100644 --- a/api/middleware/schedule/handlers/searchHandler.js +++ b/api/middleware/schedule/handlers/searchHandler.js @@ -59,16 +59,36 @@ class SearchHandler extends BaseHandler { } } - // 4. 创建搜索指令 + // 4. 从 resume_info 取 deliver_tab_label,下发给 get_job_list 用于切换期望 tab + const platformType = platform || accountConfig.platform_type || 'boss'; + let tabLabel = ''; + try { + const db = require('../../dbProxy'); + const resume_info = db.getModel('resume_info'); + const resume = await resume_info.findOne({ + where: { sn_code, platform: platformType, isActive: true }, + order: [['last_modify_time', 'DESC']], + attributes: ['deliver_tab_label'] + }); + if (resume && resume.deliver_tab_label) { + tabLabel = String(resume.deliver_tab_label).trim(); + } + } catch (e) { + console.warn('[自动搜索] 读取 resume_info.deliver_tab_label 失败:', e.message); + } + + const commandParams = { + sn_code, + keyword: keyword || accountConfig.keyword || '', + platform: platformType, + pageCount: pageCount || searchConfig.page_count || 3 + }; + if (tabLabel) commandParams.tabLabel = tabLabel; + const searchCommand = { command_type: 'get_job_list', command_name: 'get_job_list', - command_params: JSON.stringify({ - sn_code, - keyword: keyword || accountConfig.keyword || '', - platform: platform || accountConfig.platform_type || 'boss', - pageCount: pageCount || searchConfig.page_count || 3 - }), + command_params: JSON.stringify(commandParams), priority: config.getTaskPriority('search_jobs') || 8 }; diff --git a/api/middleware/schedule/services/jobFilterEngine.js b/api/middleware/schedule/services/jobFilterEngine.js index d133022..16f955b 100644 --- a/api/middleware/schedule/services/jobFilterEngine.js +++ b/api/middleware/schedule/services/jobFilterEngine.js @@ -22,21 +22,42 @@ class JobFilterEngine { let filtered = [...jobs]; // 1. 薪资过滤 + const beforeSalary = filtered.length; filtered = this.filterBySalary(filtered, config); + const salaryRemoved = beforeSalary - filtered.length; + if (salaryRemoved > 0) { + console.log(`[jobFilterEngine] 步骤1-薪资过滤: 输入${beforeSalary} 输出${filtered.length} 剔除${salaryRemoved} (范围: ${config.min_salary ?? 0}-${config.max_salary ?? 0}K)`); + } // 2. 关键词过滤 + const beforeKeywords = filtered.length; filtered = this.filterByKeywords(filtered, config); + const keywordsRemoved = beforeKeywords - filtered.length; + if (keywordsRemoved > 0) { + console.log(`[jobFilterEngine] 步骤2-关键词过滤: 输入${beforeKeywords} 输出${filtered.length} 剔除${keywordsRemoved} (排除: ${(config.exclude_keywords || []).join(',') || '无'} 包含: ${(config.filter_keywords || []).join(',') || '无'})`); + } // 3. 公司活跃度过滤 if (config.filter_inactive_companies) { + const beforeActivity = filtered.length; filtered = await this.filterByCompanyActivity(filtered, config.company_active_days || 7); + const activityRemoved = beforeActivity - filtered.length; + if (activityRemoved > 0) { + console.log(`[jobFilterEngine] 步骤3-公司活跃度过滤: 输入${beforeActivity} 输出${filtered.length} 剔除${activityRemoved}`); + } } // 4. 去重(同一公司、同一职位名称) if (config.deduplicate) { + const beforeDedup = filtered.length; filtered = this.deduplicateJobs(filtered); + const dedupRemoved = beforeDedup - filtered.length; + if (dedupRemoved > 0) { + console.log(`[jobFilterEngine] 步骤4-去重: 输入${beforeDedup} 输出${filtered.length} 剔除${dedupRemoved}`); + } } + console.log(`[jobFilterEngine] filterJobs 结束: 原始${jobs.length} 通过${filtered.length} 总剔除${jobs.length - filtered.length}`); return filtered; } diff --git a/api/model/resume_info.js b/api/model/resume_info.js index 762a3d8..0f3e53d 100644 --- a/api/model/resume_info.js +++ b/api/model/resume_info.js @@ -232,6 +232,20 @@ module.exports = (db) => { defaultValue: '' }, + // 投递用期望标签:get_job_listings 拉取的 tab 列表与当前选中的 tab 文本 + job_listings: { + comment: '简历/期望 tab 列表(JSON数组),如 ["推荐", "前端开发工程师"]', + type: Sequelize.JSON(), + allowNull: true, + defaultValue: null + }, + deliver_tab_label: { + comment: '投递时使用的标签文本,对应 job_listings 中的某一项,如 "前端开发工程师"', + type: Sequelize.STRING(100), + allowNull: true, + defaultValue: '' + }, + // 状态信息 isActive: { comment: '是否活跃简历', diff --git a/api/services/pla_account_service.js b/api/services/pla_account_service.js index b742cdc..c9e6e82 100644 --- a/api/services/pla_account_service.js +++ b/api/services/pla_account_service.js @@ -486,6 +486,23 @@ class PlaAccountService { finalParams.keyword = account.keyword; } + // get_job_list 从 resume_info 取 deliver_tab_label 作为 tabLabel 参数 + if (commandTypeSnake === 'get_job_list') { + try { + const resume_info = db.getModel('resume_info'); + const resume = await resume_info.findOne({ + where: { sn_code: account.sn_code, platform: account.platform_type, isActive: true }, + order: [['last_modify_time', 'DESC']], + attributes: ['deliver_tab_label'] + }); + if (resume && resume.deliver_tab_label) { + finalParams.tabLabel = String(resume.deliver_tab_label).trim(); + } + } catch (e) { + console.warn('[pla_account_service] 读取 resume_info.deliver_tab_label 失败:', e.message); + } + } + // 构建指令对象(与前端/后端/下发统一:只用一个名字 command_type) const command = { command_type: commandTypeSnake,