This commit is contained in:
张成
2026-02-28 13:31:32 +08:00
parent dfd3119163
commit 58c9d64e55
6 changed files with 163 additions and 33 deletions

View File

@@ -471,18 +471,33 @@ module.exports = {
const { sn_code } = body; const { sn_code } = body;
if (!sn_code) return ctx.fail('请提供设备SN码'); 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 } }); const user = await pla_account.findOne({ where: { sn_code } });
if (!user) return ctx.fail('用户不存在'); if (!user) return ctx.fail('用户不存在');
const u = user.toJSON ? user.toJSON() : user; const u = user.toJSON ? user.toJSON() : user;
return ctx.success({ const result = {
deliver_config: u.deliver_config || null, deliver_config: u.deliver_config || null,
chat_strategy: u.chat_strategy || null, chat_strategy: u.chat_strategy || null,
active_actions: u.active_actions || null, active_actions: u.active_actions || null,
auto_chat: u.auto_chat != null ? !!u.auto_chat : false, auto_chat: u.auto_chat != null ? !!u.auto_chat : false,
auto_active: u.auto_active != null ? !!u.auto_active : 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) { } catch (error) {
console.error('[获取账号配置失败]', error); console.error('[获取账号配置失败]', error);
return ctx.fail('获取账号配置失败'); return ctx.fail('获取账号配置失败');
@@ -495,10 +510,10 @@ module.exports = {
'POST /user/account-config/save': async (ctx) => { 'POST /user/account-config/save': async (ctx) => {
try { try {
const body = ctx.getBody(); 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码'); 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 } }); const user = await pla_account.findOne({ where: { sn_code } });
if (!user) return ctx.fail('用户不存在'); if (!user) return ctx.fail('用户不存在');
@@ -529,9 +544,37 @@ module.exports = {
updateData.active_actions = active_actions; updateData.active_actions = active_actions;
if (active_actions.auto_active !== undefined) updateData.auto_active = active_actions.auto_active ? 1 : 0; 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
});
}
}
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 } }); await pla_account.update(updateData, { where: { id: user.id } });
}
return ctx.success({ message: '配置保存成功' }); return ctx.success({ message: '配置保存成功' });
} catch (error) { } catch (error) {
console.error('[保存账号配置失败]', error); console.error('[保存账号配置失败]', error);

View File

@@ -85,8 +85,9 @@ class DeliverHandler extends BaseHandler {
// 5. 获取职位类型配置 // 5. 获取职位类型配置
const jobTypeConfig = await this.getJobTypeConfig(accountConfig.job_type_id); const jobTypeConfig = await this.getJobTypeConfig(accountConfig.job_type_id);
// 6. 搜索职位列表 // 6. 搜索职位列表(从 resume_info 取 deliver_tab_label 作为 tabLabel 下发给 get_job_list 切换期望 tab
await this.searchJobs(sn_code, platform, keyword || accountConfig.keyword, pageCount, task.id); const tabLabel = resume.deliver_tab_label || '';
await this.searchJobs(sn_code, platform, keyword || accountConfig.keyword, pageCount, task.id, tabLabel);
// 7. 从数据库获取待投递职位 // 7. 从数据库获取待投递职位
const pendingJobs = await this.getPendingJobs(sn_code, platform, actualMaxCount * 3); 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_labelget_job_list 会按此选择 tab
*/ */
async searchJobs(sn_code, platform, keyword, pageCount, taskId) { async searchJobs(sn_code, platform, keyword, pageCount, taskId, tabLabel = '') {
const getJobListCommand = { const params = {
command_type: 'get_job_list',
command_name: 'get_job_list',
command_params: JSON.stringify({
sn_code, sn_code,
keyword, keyword,
platform, platform,
pageCount 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(params),
priority: config.getTaskPriority('search_jobs') || 5 priority: config.getTaskPriority('search_jobs') || 5
}; };
@@ -337,18 +343,22 @@ class DeliverHandler extends BaseHandler {
*/ */
async filterAndScoreJobs(jobs, resume, accountConfig, jobTypeConfig, filterConfig, recentCompanies) { async filterAndScoreJobs(jobs, resume, accountConfig, jobTypeConfig, filterConfig, recentCompanies) {
const scored = []; const scored = [];
const jobDesc = (j) => `${j.companyName || '?'} / ${j.jobTitle || '?'}`;
console.log(`[自动投递-过滤] 开始过滤与评分,待处理职位数: ${jobs.length}`);
for (const job of jobs) { for (const job of jobs) {
// 1. 过滤近期已投递的公司 // 1. 过滤近期已投递的公司
if (job.companyName && recentCompanies.has(job.companyName)) { if (job.companyName && recentCompanies.has(job.companyName)) {
console.log(`[自动投递] 跳过已投递公司: ${job.companyName}`); console.log(`[自动投递-过滤] 步骤1-已投递公司 剔除: ${jobDesc(job)}`);
continue; continue;
} }
// 2. 使用 jobFilterEngine 过滤和评分 // 2. 使用 jobFilterEngine 过滤和评分
const filtered = await jobFilterEngine.filterJobs([job], filterConfig, resume); const filtered = await jobFilterEngine.filterJobs([job], filterConfig, resume);
if (filtered.length === 0) { if (filtered.length === 0) {
continue; // 不符合过滤条件 console.log(`[自动投递-过滤] 步骤2-jobFilterEngine 剔除: ${jobDesc(job)} (不满足过滤条件)`);
continue;
} }
// 3. 使用原有的评分系统job_filter_service计算详细分数 // 3. 使用原有的评分系统job_filter_service计算详细分数
@@ -371,7 +381,11 @@ class DeliverHandler extends BaseHandler {
const finalScore = scoreResult.totalScore + keywordBonus.score; const finalScore = scoreResult.totalScore + keywordBonus.score;
// 5. 只保留评分 >= 60 的职位 // 5. 只保留评分 >= 60 的职位
if (finalScore >= 60) { if (finalScore < 60) {
console.log(`[自动投递-过滤] 步骤5-评分不足(>=60) 剔除: ${jobDesc(job)} | 总分=${finalScore.toFixed(1)} (基础=${scoreResult.totalScore?.toFixed(1)}, 关键词奖励=${keywordBonus.score})`);
continue;
}
scored.push({ scored.push({
...job, ...job,
matchScore: finalScore, matchScore: finalScore,
@@ -380,12 +394,13 @@ class DeliverHandler extends BaseHandler {
keywordBonus: keywordBonus.score keywordBonus: keywordBonus.score
} }
}); });
} console.log(`[自动投递-过滤] 通过: ${jobDesc(job)} | 总分=${finalScore.toFixed(1)}`);
} }
// 按评分降序排序 // 按评分降序排序
scored.sort((a, b) => b.matchScore - a.matchScore); scored.sort((a, b) => b.matchScore - a.matchScore);
console.log(`[自动投递-过滤] 结束: 原始=${jobs.length}, 通过=${scored.length}, 剔除=${jobs.length - scored.length}`);
return scored; return scored;
} }

View File

@@ -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 = { const searchCommand = {
command_type: 'get_job_list', command_type: 'get_job_list',
command_name: 'get_job_list', command_name: 'get_job_list',
command_params: JSON.stringify({ command_params: JSON.stringify(commandParams),
sn_code,
keyword: keyword || accountConfig.keyword || '',
platform: platform || accountConfig.platform_type || 'boss',
pageCount: pageCount || searchConfig.page_count || 3
}),
priority: config.getTaskPriority('search_jobs') || 8 priority: config.getTaskPriority('search_jobs') || 8
}; };

View File

@@ -22,21 +22,42 @@ class JobFilterEngine {
let filtered = [...jobs]; let filtered = [...jobs];
// 1. 薪资过滤 // 1. 薪资过滤
const beforeSalary = filtered.length;
filtered = this.filterBySalary(filtered, config); 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. 关键词过滤 // 2. 关键词过滤
const beforeKeywords = filtered.length;
filtered = this.filterByKeywords(filtered, config); 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. 公司活跃度过滤 // 3. 公司活跃度过滤
if (config.filter_inactive_companies) { if (config.filter_inactive_companies) {
const beforeActivity = filtered.length;
filtered = await this.filterByCompanyActivity(filtered, config.company_active_days || 7); 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. 去重(同一公司、同一职位名称) // 4. 去重(同一公司、同一职位名称)
if (config.deduplicate) { if (config.deduplicate) {
const beforeDedup = filtered.length;
filtered = this.deduplicateJobs(filtered); 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; return filtered;
} }

View File

@@ -232,6 +232,20 @@ module.exports = (db) => {
defaultValue: '' 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: { isActive: {
comment: '是否活跃简历', comment: '是否活跃简历',

View File

@@ -486,6 +486,23 @@ class PlaAccountService {
finalParams.keyword = account.keyword; 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 // 构建指令对象(与前端/后端/下发统一:只用一个名字 command_type
const command = { const command = {
command_type: commandTypeSnake, command_type: commandTypeSnake,