1
This commit is contained in:
@@ -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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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: '配置保存成功' });
|
return ctx.success({ message: '配置保存成功' });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[保存账号配置失败]', error);
|
console.error('[保存账号配置失败]', error);
|
||||||
|
|||||||
@@ -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_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 = {
|
const getJobListCommand = {
|
||||||
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(params),
|
||||||
sn_code,
|
|
||||||
keyword,
|
|
||||||
platform,
|
|
||||||
pageCount
|
|
||||||
}),
|
|
||||||
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,21 +381,26 @@ 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) {
|
||||||
scored.push({
|
console.log(`[自动投递-过滤] 步骤5-评分不足(>=60) 剔除: ${jobDesc(job)} | 总分=${finalScore.toFixed(1)} (基础=${scoreResult.totalScore?.toFixed(1)}, 关键词奖励=${keywordBonus.score})`);
|
||||||
...job,
|
continue;
|
||||||
matchScore: finalScore,
|
|
||||||
scoreDetails: {
|
|
||||||
...scoreResult.scores,
|
|
||||||
keywordBonus: keywordBonus.score
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
scored.sort((a, b) => b.matchScore - a.matchScore);
|
||||||
|
|
||||||
|
console.log(`[自动投递-过滤] 结束: 原始=${jobs.length}, 通过=${scored.length}, 剔除=${jobs.length - scored.length}`);
|
||||||
return scored;
|
return scored;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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: '是否活跃简历',
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user