1
This commit is contained in:
@@ -32,15 +32,16 @@ class JobFilterService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据职位类型ID获取技能关键词和排除关键词
|
||||
* 根据职位类型ID获取技能关键词、排除关键词、标题须含词
|
||||
* @param {number} jobTypeId - 职位类型ID
|
||||
* @returns {Promise<{commonSkills: Array, excludeKeywords: Array}>}
|
||||
* @returns {Promise<{commonSkills: Array, excludeKeywords: Array, titleIncludeKeywords: Array}>}
|
||||
*/
|
||||
async getJobTypeConfig(jobTypeId) {
|
||||
if (!jobTypeId) {
|
||||
return {
|
||||
commonSkills: this.defaultCommonSkills,
|
||||
excludeKeywords: this.defaultExcludeKeywords
|
||||
excludeKeywords: this.defaultExcludeKeywords,
|
||||
titleIncludeKeywords: []
|
||||
};
|
||||
}
|
||||
|
||||
@@ -55,7 +56,8 @@ class JobFilterService {
|
||||
console.warn('[职位过滤服务] job_types 模型不存在,使用默认配置');
|
||||
return {
|
||||
commonSkills: this.defaultCommonSkills,
|
||||
excludeKeywords: this.defaultExcludeKeywords
|
||||
excludeKeywords: this.defaultExcludeKeywords,
|
||||
titleIncludeKeywords: []
|
||||
};
|
||||
}
|
||||
|
||||
@@ -67,7 +69,8 @@ class JobFilterService {
|
||||
console.warn(`[职位过滤服务] 职位类型 ${jobTypeId} 不存在或已禁用,使用默认配置`);
|
||||
return {
|
||||
commonSkills: this.defaultCommonSkills,
|
||||
excludeKeywords: this.defaultExcludeKeywords
|
||||
excludeKeywords: this.defaultExcludeKeywords,
|
||||
titleIncludeKeywords: []
|
||||
};
|
||||
}
|
||||
|
||||
@@ -76,6 +79,7 @@ class JobFilterService {
|
||||
// 解析 JSON 字段
|
||||
let commonSkills = this.defaultCommonSkills;
|
||||
let excludeKeywords = this.defaultExcludeKeywords;
|
||||
let titleIncludeKeywords = [];
|
||||
|
||||
if (jobTypeData.commonSkills) {
|
||||
try {
|
||||
@@ -103,9 +107,23 @@ class JobFilterService {
|
||||
}
|
||||
}
|
||||
|
||||
if (jobTypeData.titleIncludeKeywords) {
|
||||
try {
|
||||
const parsed = typeof jobTypeData.titleIncludeKeywords === 'string'
|
||||
? JSON.parse(jobTypeData.titleIncludeKeywords)
|
||||
: jobTypeData.titleIncludeKeywords;
|
||||
if (Array.isArray(parsed)) {
|
||||
titleIncludeKeywords = parsed.map((k) => String(k || '').trim()).filter(Boolean);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(`[职位过滤服务] 解析 titleIncludeKeywords 失败:`, e);
|
||||
}
|
||||
}
|
||||
|
||||
const config = {
|
||||
commonSkills,
|
||||
excludeKeywords
|
||||
excludeKeywords,
|
||||
titleIncludeKeywords
|
||||
};
|
||||
|
||||
// 缓存配置(缓存5分钟)
|
||||
@@ -119,7 +137,8 @@ class JobFilterService {
|
||||
console.error(`[职位过滤服务] 获取职位类型配置失败:`, error);
|
||||
return {
|
||||
commonSkills: this.defaultCommonSkills,
|
||||
excludeKeywords: this.defaultExcludeKeywords
|
||||
excludeKeywords: this.defaultExcludeKeywords,
|
||||
titleIncludeKeywords: []
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ class ScheduledJobs {
|
||||
this.jobs.push(autoActiveJob);
|
||||
console.log('[定时任务] ✓ 已启动自动活跃任务 (每2小时)');
|
||||
|
||||
// 5. 每日拉取 get_job_listings 并用 AI 更新 job_types(description / excludeKeywords / commonSkills)
|
||||
// 5. 每日拉取 get_job_listings 并用 AI 更新 job_types(description / excludeKeywords / commonSkills / titleIncludeKeywords)
|
||||
const jobTypeListingsAiJob = node_schedule.scheduleJob(config.schedules.jobTypeListingsAi || '0 0 4 * * *', () => {
|
||||
this.runDailyJobTypeListingsAiSync().catch((err) => {
|
||||
console.error('[定时任务] 每日 job_types AI 同步失败:', err);
|
||||
|
||||
@@ -340,9 +340,27 @@ class DeliverHandler extends BaseHandler {
|
||||
? { min: filterRules.minSalary || 0, max: filterRules.maxSalary || 0 }
|
||||
: ConfigManager.getSalaryRange(deliverConfig);
|
||||
|
||||
let title_include_keywords = [];
|
||||
if (jobTypeConfig && jobTypeConfig.titleIncludeKeywords != null) {
|
||||
const v = jobTypeConfig.titleIncludeKeywords;
|
||||
if (Array.isArray(v)) {
|
||||
title_include_keywords = v.map((k) => String(k || '').trim()).filter(Boolean);
|
||||
} else if (typeof v === 'string' && v.trim()) {
|
||||
try {
|
||||
const p = JSON.parse(v);
|
||||
if (Array.isArray(p)) {
|
||||
title_include_keywords = p.map((k) => String(k || '').trim()).filter(Boolean);
|
||||
}
|
||||
} catch (e) {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
exclude_keywords: [...jobTypeExclude, ...deliverExclude, ...filterExclude],
|
||||
filter_keywords: filterKeywords.length > 0 ? filterKeywords : deliverFilter,
|
||||
title_include_keywords,
|
||||
min_salary: salaryRange.min,
|
||||
max_salary: salaryRange.max,
|
||||
priority_weights: ConfigManager.getPriorityWeights(deliverConfig)
|
||||
|
||||
@@ -9,7 +9,7 @@ const db = require('../../dbProxy');
|
||||
*/
|
||||
class JobFilterEngine {
|
||||
/**
|
||||
* 过滤职位列表(薪资 → 关键词 → 活跃度 → 去重)
|
||||
* 过滤职位列表(薪资 → 标题须含词 → 关键词 → 活跃度 → 去重)
|
||||
* @param {Array} jobs - 职位列表
|
||||
* @param {object} config - 过滤配置
|
||||
* @param {object} resumeInfo - 简历信息(未使用,兼容签名)
|
||||
@@ -30,31 +30,39 @@ class JobFilterEngine {
|
||||
console.log(`[jobFilterEngine] 步骤1-薪资过滤: 输入${beforeSalary} 输出${filtered.length} 剔除${salaryRemoved} (范围: ${config.min_salary ?? 0}-${config.max_salary ?? 0}K)`);
|
||||
}
|
||||
|
||||
// 2. 关键词过滤
|
||||
// 2. 职位标题须包含(job_types.titleIncludeKeywords,仅 jobTitle/jobName/name,与 commonSkills 无关)
|
||||
const beforeTitleKw = filtered.length;
|
||||
filtered = this.filterByTitleIncludeKeywords(filtered, config);
|
||||
const titleKwRemoved = beforeTitleKw - filtered.length;
|
||||
if (titleKwRemoved > 0) {
|
||||
console.log(`[jobFilterEngine] 步骤2-标题须含: 输入${beforeTitleKw} 输出${filtered.length} 剔除${titleKwRemoved} (须同时含: ${(config.title_include_keywords || []).join(' · ') || '无'})`);
|
||||
}
|
||||
|
||||
// 3. 关键词过滤(排除词 + filter_keywords,匹配标题与行业等)
|
||||
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(',') || '无'})`);
|
||||
console.log(`[jobFilterEngine] 步骤3-关键词过滤: 输入${beforeKeywords} 输出${filtered.length} 剔除${keywordsRemoved} (排除: ${(config.exclude_keywords || []).join(',') || '无'} 包含: ${(config.filter_keywords || []).join(',') || '无'})`);
|
||||
}
|
||||
|
||||
// 3. 公司活跃度过滤
|
||||
// 4. 公司活跃度过滤
|
||||
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}`);
|
||||
console.log(`[jobFilterEngine] 步骤4-公司活跃度过滤: 输入${beforeActivity} 输出${filtered.length} 剔除${activityRemoved}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 去重(同一公司、同一职位名称)
|
||||
// 5. 去重(同一公司、同一职位名称)
|
||||
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] 步骤5-去重: 输入${beforeDedup} 输出${filtered.length} 剔除${dedupRemoved}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,6 +142,29 @@ class JobFilterEngine {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 职位标题须包含配置中的每个子串(AND 关系),不扫描描述/公司名/commonSkills
|
||||
* @param {Array} jobs
|
||||
* @param {object} config
|
||||
* @returns {Array}
|
||||
*/
|
||||
filterByTitleIncludeKeywords(jobs, config) {
|
||||
const kws = config.title_include_keywords;
|
||||
if (!Array.isArray(kws) || kws.length === 0) {
|
||||
return jobs;
|
||||
}
|
||||
return jobs.filter((job) => {
|
||||
const title = `${job.jobTitle || job.jobName || job.name || ''}`.toLowerCase();
|
||||
return kws.every((kw) => {
|
||||
const k = String(kw || '').toLowerCase().trim();
|
||||
if (!k) {
|
||||
return true;
|
||||
}
|
||||
return title.includes(k);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 按关键词过滤
|
||||
* @param {Array} jobs - 职位列表
|
||||
|
||||
Reference in New Issue
Block a user