This commit is contained in:
张成
2025-12-25 13:10:54 +08:00
parent 36465d81e3
commit c6c78d0c43
17 changed files with 4857 additions and 68 deletions

View File

@@ -3,19 +3,21 @@ const config = require('../../../config/config');
const logs = require('../logProxy');
/**
* DeepSeek大模型服务
* 集成DeepSeek API提供智能化的岗位筛选、聊天生成、简历分析等功能
* Qwen 2.5 大模型服务
* 集成阿里云 DashScope API提供智能化的岗位筛选、聊天生成、简历分析等功能
*/
class aiService {
constructor() {
this.apiKey = config.deepseekApiKey || process.env.DEEPSEEK_API_KEY;
this.apiUrl = config.deepseekApiUrl || 'https://api.deepseek.com/v1/chat/completions';
this.model = config.deepseekModel || 'deepseek-chat';
this.apiKey = config.ai?.apiKey || process.env.DASHSCOPE_API_KEY;
// 使用 DashScope 兼容 OpenAI 格式的接口
this.apiUrl = 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions';
// Qwen 2.5 模型qwen-turbo快速、qwen-plus增强、qwen-max最强
this.model = config.ai?.model || 'qwen-turbo';
this.maxRetries = 3;
}
/**
* 调用DeepSeek API
* 调用 Qwen 2.5 API
* @param {string} prompt - 提示词
* @param {object} options - 配置选项
* @returns {Promise<object>} API响应结果
@@ -42,7 +44,7 @@ class aiService {
try {
const response = await axios.post(this.apiUrl, requestData, {
headers: {
'Authorization': `${this.apiKey}`,
'Authorization': `Bearer ${this.apiKey}`, // DashScope 使用 Bearer token 格式
'Content-Type': 'application/json'
},
timeout: 30000
@@ -53,7 +55,7 @@ class aiService {
content: response.data.choices?.[0]?.message?.content || ''
};
} catch (error) {
console.log(`DeepSeek API调用失败 (尝试 ${attempt}/${this.maxRetries}): ${error.message}`);
console.log(`Qwen 2.5 API调用失败 (尝试 ${attempt}/${this.maxRetries}): ${error.message}`);
if (attempt === this.maxRetries) {
throw new Error(error.message);

View File

@@ -136,14 +136,51 @@ class JobFilterService {
}
}
/**
* 保存匹配分析结果到数据库
* @param {string|number} jobPostingId - 职位IDjob_postings表的主键ID
* @param {object} analysisResult - 分析结果
* @returns {Promise<boolean>} 是否保存成功
*/
async saveMatchAnalysisToDatabase(jobPostingId, analysisResult) {
try {
const job_postings = db.getModel('job_postings');
if (!job_postings) {
console.warn('[职位过滤服务] job_postings 模型不存在,跳过保存');
return false;
}
const updateData = {
textMatchScore: analysisResult.overallScore || 0,
isOutsourcing: analysisResult.isOutsourcing || false,
matchSuggestion: analysisResult.suggestion || '',
matchConcerns: JSON.stringify(analysisResult.concerns || []),
textMatchAnalysis: JSON.stringify(analysisResult.analysis || analysisResult)
};
await job_postings.update(updateData, {
where: { id: jobPostingId }
});
console.log(`[职位过滤服务] 已保存职位 ${jobPostingId} 的匹配分析结果,综合评分: ${analysisResult.overallScore}`);
return true;
} catch (error) {
console.error(`[职位过滤服务] 保存匹配分析结果失败:`, error);
return false;
}
}
/**
* 使用文本匹配分析职位与简历的匹配度
* @param {object} jobInfo - 职位信息
* @param {object} resumeInfo - 简历信息(可选)
* @param {number} jobTypeId - 职位类型ID可选
* @param {object} options - 选项
* @param {number} options.jobPostingId - 职位IDjob_postings表的主键ID如果提供则自动保存到数据库
* @param {boolean} options.autoSave - 是否自动保存到数据库默认false
* @returns {Promise<object>} 匹配度分析结果
*/
async analyzeJobMatch(jobInfo, resumeInfo = {}, jobTypeId = null) {
async analyzeJobMatch(jobInfo, resumeInfo = {}, jobTypeId = null, options = {}) {
const jobText = this.buildJobText(jobInfo);
const resumeText = this.buildResumeText(resumeInfo);
@@ -178,7 +215,7 @@ class JobFilterService {
// 8. 投递建议
const suggestion = this.getSuggestion(overallScore, isOutsourcing, concerns);
return {
const result = {
skillMatch: skillScore,
experienceMatch: experienceScore,
salaryMatch: salaryScore,
@@ -198,6 +235,13 @@ class JobFilterService {
suggestion: suggestion
}
};
// 如果提供了 jobPostingId 且 autoSave 为 true自动保存到数据库
if (options.jobPostingId && options.autoSave !== false) {
await this.saveMatchAnalysisToDatabase(options.jobPostingId, result);
}
return result;
}
/**
@@ -453,21 +497,39 @@ class JobFilterService {
* @returns {string} 投递建议
*/
getSuggestion(overallScore, isOutsourcing, concerns) {
const concernsCount = concerns ? concerns.length : 0;
// 不推荐:外包 或 关注点过多(>2
if (isOutsourcing) {
return '谨慎投递:可能是外包岗位';
return '不推荐投递:外包岗位';
}
if (concerns.length > 2) {
if (concernsCount > 2) {
return '不推荐投递:存在多个关注点';
}
if (overallScore >= 80) {
return '强烈推荐投递:匹配度很高';
} else if (overallScore >= 60) {
return '可以投递:匹配度中等';
} else {
return '谨慎投递:匹配度较低';
// 不推荐:< 60 分
if (overallScore < 60) {
return '不推荐投递:匹配度较低';
}
// 优先级1必须投递≥ 80 分,且无关注点,非外包
if (overallScore >= 80 && concernsCount === 0) {
return '必须投递:匹配度很高,无关注点';
}
// 优先级2可以投递60-79 分,关注点 ≤ 2 个,非外包(优先处理无关注点情况)
if (overallScore >= 60 && overallScore < 80 && concernsCount === 0) {
return '可以投递:匹配度中等,无关注点';
}
// 优先级3谨慎考虑60-79 分但有关注点,或 ≥ 80 分但有关注点
if (overallScore >= 60 && concernsCount > 0 && concernsCount <= 2) {
return '谨慎考虑:存在关注点或特殊情况';
}
// 兜底情况
return '谨慎考虑:需要进一步评估';
}
/**
@@ -599,15 +661,19 @@ class JobFilterService {
* @param {object} filterRules - 过滤规则
* @param {object} resumeInfo - 简历信息(可选)
* @param {number} jobTypeId - 职位类型ID可选
* @param {object} options - 选项
* @param {boolean} options.autoSave - 是否自动保存评分结果到数据库默认false
* @returns {Promise<Array>} 过滤后的职位列表(带匹配分数)
*/
async filterJobs(jobs, filterRules = {}, resumeInfo = {}, jobTypeId = null) {
async filterJobs(jobs, filterRules = {}, resumeInfo = {}, jobTypeId = null, options = {}) {
const {
minScore = 60, // 最低匹配分数
excludeOutsourcing = true, // 是否排除外包
excludeKeywords = [] // 额外排除关键词
} = filterRules;
const { autoSave = false } = options;
// 获取职位类型配置
const { excludeKeywords: typeExcludeKeywords } = await this.getJobTypeConfig(jobTypeId);
const allExcludeKeywords = [...typeExcludeKeywords, ...excludeKeywords];
@@ -616,8 +682,13 @@ class JobFilterService {
for (const job of jobs) {
const jobData = job.toJSON ? job.toJSON() : job;
// 分析匹配度
const analysis = await this.analyzeJobMatch(jobData, resumeInfo, jobTypeId);
// 分析匹配度(如果 autoSave 为 true 且 job 有 id则自动保存
const analysisOptions = autoSave && jobData.id ? {
jobPostingId: jobData.id,
autoSave: true
} : {};
const analysis = await this.analyzeJobMatch(jobData, resumeInfo, jobTypeId, analysisOptions);
results.push({
...jobData,
@@ -662,11 +733,29 @@ class JobFilterService {
const scores = {};
let totalScore = 0;
// 解析权重配置
// 解析权重配置根据排序优先级规范化权重确保总和为100
const weights = {};
priorityWeights.forEach(item => {
weights[item.key] = item.weight / 100; // 转换为小数
});
if (Array.isArray(priorityWeights) && priorityWeights.length > 0) {
// 计算权重总和
const totalWeight = priorityWeights.reduce((sum, item) => sum + (Number(item.weight) || 0), 0);
// 如果总和大于0按比例规范化权重总和归一化为1即100%
if (totalWeight > 0) {
priorityWeights.forEach(item => {
weights[item.key] = (Number(item.weight) || 0) / totalWeight; // 规范化权重0-1之间
});
} else {
// 如果权重总和为0或未设置按照排序顺序分配权重第一个优先级最高
// 使用等差数列递减:第一个权重最高,依次递减
const n = priorityWeights.length;
const totalSequentialWeight = (n * (n + 1)) / 2; // 1+2+...+n
priorityWeights.forEach((item, index) => {
// 按照排序顺序,第一个权重最高,依次递减(权重比例为 n, n-1, n-2, ..., 1
const sequentialWeight = n - index;
weights[item.key] = sequentialWeight / totalSequentialWeight;
});
}
}
// 1. 距离评分(基于经纬度)
if (weights.distance) {

View File

@@ -0,0 +1,651 @@
# 职位过滤服务文档
## 概述
`job_filter_service.js` 是一个职位文本匹配过滤服务,使用简单的文本匹配规则来过滤和分析职位信息。该服务支持从数据库动态获取职位类型的技能关键词和排除关键词,能够分析职位与简历的匹配度,并提供过滤和评分功能。
## 主要功能
1. **职位类型配置管理**:从数据库获取或使用默认配置的技能关键词和排除关键词
2. **匹配度分析**:分析职位与简历的匹配度(技能、经验、薪资)
3. **职位过滤**:根据匹配分数、外包标识、排除关键词等条件过滤职位列表
4. **自定义权重评分**:支持根据自定义权重配置计算职位评分(距离、薪资、工作年限、学历、技能)
## 类结构
```javascript
class JobFilterService {
// 默认技能关键词
defaultCommonSkills: Array<string>
// 默认排除关键词
defaultExcludeKeywords: Array<string>
// 职位类型配置缓存
jobTypeCache: Map<number, Object>
}
```
## API 文档
### 1. getJobTypeConfig(jobTypeId)
根据职位类型ID获取技能关键词和排除关键词配置。
**参数:**
- `jobTypeId` (number, 可选): 职位类型ID
**返回值:**
```javascript
{
commonSkills: Array<string>, // 技能关键词列表
excludeKeywords: Array<string> // 排除关键词列表
}
```
**说明:**
- 如果未提供 `jobTypeId` 或配置获取失败,返回默认配置
- 配置结果会缓存5分钟避免频繁查询数据库
-`job_types` 表读取 `commonSkills``excludeKeywords` 字段JSON格式
**使用示例:**
```javascript
const config = await jobFilterService.getJobTypeConfig(1);
console.log(config.commonSkills); // ['Vue', 'React', ...]
console.log(config.excludeKeywords); // ['外包', '外派', ...]
```
---
### 2. clearCache(jobTypeId)
清除职位类型配置缓存。
**参数:**
- `jobTypeId` (number, 可选): 职位类型ID不传则清除所有缓存
**使用示例:**
```javascript
// 清除特定职位类型的缓存
jobFilterService.clearCache(1);
// 清除所有缓存
jobFilterService.clearCache();
```
---
### 3. analyzeJobMatch(jobInfo, resumeInfo, jobTypeId)
使用文本匹配分析职位与简历的匹配度。
**参数:**
- `jobInfo` (object, 必需): 职位信息对象
- `jobTitle` (string): 职位名称
- `companyName` (string): 公司名称
- `description` (string): 职位描述
- `skills` (string): 技能要求
- `requirements` (string): 职位要求
- `salary` (string): 薪资范围
- `experience` (string): 经验要求
- `education` (string): 学历要求
- `longitude` (number): 经度
- `latitude` (number): 纬度
- `resumeInfo` (object, 可选): 简历信息对象
- `skills` (string|Array): 技能列表
- `skillDescription` (string): 技能描述
- `currentPosition` (string): 当前职位
- `expectedPosition` (string): 期望职位
- `workYears` (number|string): 工作年限
- `expectedSalary` (string): 期望薪资
- `education` (string): 学历
- `jobTypeId` (number, 可选): 职位类型ID
**返回值:**
```javascript
{
skillMatch: number, // 技能匹配度0-100
experienceMatch: number, // 经验匹配度0-100
salaryMatch: number, // 薪资匹配度0-100
isOutsourcing: boolean, // 是否为外包岗位
overallScore: number, // 综合推荐指数0-100
matchReasons: Array<string>, // 匹配原因列表
concerns: Array<string>, // 关注点列表
suggestion: string, // 投递建议
analysis: Object // 完整的分析结果(同上)
}
```
**评分规则:**
1. **综合推荐指数计算**
- 技能匹配度 × 40% + 经验匹配度 × 30% + 薪资匹配度 × 30%
2. **技能匹配度**
- 从职位描述中提取技能关键词
- 计算简历中匹配的技能数量占比
- 无简历信息时,基于职位关键词数量评分
3. **经验匹配度**
- 从职位描述中提取经验要求应届、1年、2年、3年、5年、10年
- 根据简历工作年限与要求的匹配程度评分
4. **薪资匹配度**
- 解析职位薪资范围和期望薪资
- 期望薪资低于职位薪资时得高分,高于职位薪资时得低分
5. **外包检测**
- 检测职位描述中是否包含:外包、外派、驻场、人力外包、项目外包
**使用示例:**
```javascript
const jobInfo = {
jobTitle: '前端开发工程师',
description: '要求3年以上Vue开发经验熟悉React',
salary: '15-25K',
experience: '3-5年'
};
const resumeInfo = {
skills: 'Vue, React, JavaScript',
workYears: 4,
expectedSalary: '20K'
};
const result = await jobFilterService.analyzeJobMatch(jobInfo, resumeInfo, 1);
console.log(result.overallScore); // 85
console.log(result.matchReasons); // ['技能匹配度高', '工作经验符合要求', ...]
console.log(result.suggestion); // '强烈推荐投递:匹配度很高'
```
---
### 4. filterJobs(jobs, filterRules, resumeInfo, jobTypeId)
过滤职位列表,返回匹配的职位(带匹配分数)。
**参数:**
- `jobs` (Array, 必需): 职位列表(可以是 Sequelize 模型实例或普通对象)
- `filterRules` (object, 可选): 过滤规则
- `minScore` (number, 默认60): 最低匹配分数
- `excludeOutsourcing` (boolean, 默认true): 是否排除外包岗位
- `excludeKeywords` (Array<string>, 默认[]): 额外排除关键词列表
- `resumeInfo` (object, 可选): 简历信息
- `jobTypeId` (number, 可选): 职位类型ID
**返回值:**
```javascript
Array<{
...jobData, // 原始职位数据
matchScore: number, // 匹配分数
matchAnalysis: Object // 完整的匹配分析结果
}>
```
**过滤逻辑:**
1. 对每个职位进行匹配度分析
2. 过滤掉匹配分数低于 `minScore` 的职位
3. 如果 `excludeOutsourcing` 为 true过滤掉外包岗位
4. 过滤掉包含排除关键词的职位
5. 按匹配分数降序排序
**使用示例:**
```javascript
const jobs = [
{ jobTitle: '前端开发', description: 'Vue开发...', salary: '20K' },
{ jobTitle: '后端开发', description: 'Java开发...', salary: '25K' }
];
const resumeInfo = {
skills: 'Vue, JavaScript',
workYears: 3
};
const filterRules = {
minScore: 70,
excludeOutsourcing: true,
excludeKeywords: ['销售']
};
const filteredJobs = await jobFilterService.filterJobs(
jobs,
filterRules,
resumeInfo,
1
);
console.log(filteredJobs.length); // 过滤后的职位数量
console.log(filteredJobs[0].matchScore); // 第一个职位的匹配分数
```
---
### 5. calculateJobScoreWithWeights(jobData, resumeInfo, accountConfig, jobTypeConfig, priorityWeights)
根据自定义权重配置计算职位评分。
**参数:**
- `jobData` (object, 必需): 职位数据
- `longitude` (number): 经度
- `latitude` (number): 纬度
- `salary` (string): 薪资范围
- `experience` (string): 经验要求
- `education` (string): 学历要求
- `resumeInfo` (object, 必需): 简历信息
- `expectedSalary` (string): 期望薪资
- `workYears` (string|number): 工作年限
- `education` (string): 学历
- `skills` (string|Array): 技能列表
- `accountConfig` (object, 必需): 账号配置
- `user_longitude` (number): 用户经度
- `user_latitude` (number): 用户纬度
- `jobTypeConfig` (object, 可选): 职位类型配置(包含 commonSkills
- `priorityWeights` (Array, 必需): 权重配置
```javascript
[
{ key: 'distance', weight: 30 }, // 距离权重0-100
{ key: 'salary', weight: 40 }, // 薪资权重
{ key: 'work_years', weight: 20 }, // 工作年限权重
{ key: 'education', weight: 10 } // 学历权重
]
```
**返回值:**
```javascript
{
totalScore: number, // 总分0-100+,技能评分作为额外加分项)
scores: {
distance: number, // 距离评分
salary: number, // 薪资评分
work_years: number, // 工作年限评分
education: number, // 学历评分
skills: number // 技能评分(如果有 jobTypeConfig
}
}
```
**评分规则:**
1. **距离评分**
- 0-5km: 100分
- 5-10km: 90分
- 10-20km: 80分
- 20-50km: 60分
- 50km以上: 30分
2. **薪资评分**
- 职位薪资 ≥ 期望薪资: 100分
- 职位薪资 ≥ 期望薪资 × 0.8: 80分
- 职位薪资 ≥ 期望薪资 × 0.6: 60分
- 其他: 40分
3. **工作年限评分**
- 简历年限 ≥ 职位要求: 100分
- 简历年限 ≥ 职位要求 × 0.8: 80分
- 简历年限 ≥ 职位要求 × 0.6: 60分
- 其他: 40分
4. **学历评分**
- 简历学历 ≥ 职位要求: 100分
- 简历学历 = 职位要求 - 1级: 70分
- 其他: 40分
5. **技能评分**
- 计算简历技能与职位类型配置的技能关键词匹配度
- 作为额外加分项固定权重10%
**使用示例:**
```javascript
const jobData = {
longitude: 116.3974,
latitude: 39.9093,
salary: '20-30K',
experience: '3-5年',
education: '本科'
};
const resumeInfo = {
expectedSalary: '25K',
workYears: 4,
education: '本科',
skills: ['Vue', 'React', 'JavaScript']
};
const accountConfig = {
user_longitude: 116.4074,
user_latitude: 39.9042
};
const jobTypeConfig = {
commonSkills: ['Vue', 'React', 'JavaScript', 'Node.js']
};
const priorityWeights = [
{ key: 'distance', weight: 30 },
{ key: 'salary', weight: 40 },
{ key: 'work_years', weight: 20 },
{ key: 'education', weight: 10 }
];
const result = jobFilterService.calculateJobScoreWithWeights(
jobData,
resumeInfo,
accountConfig,
jobTypeConfig,
priorityWeights
);
console.log(result.totalScore); // 85
console.log(result.scores); // { distance: 100, salary: 90, work_years: 100, ... }
```
---
### 6. parseSalaryRange(salaryDesc)
解析薪资范围字符串。
**参数:**
- `salaryDesc` (string): 薪资描述字符串
- 支持格式:`15-25K`、`25K`、`5000-6000元/月`、`2-3万`、`20000-30000` 等
**返回值:**
```javascript
{
min: number, // 最低薪资(元)
max: number // 最高薪资(元)
}
```
或 `null`(解析失败时)
**使用示例:**
```javascript
const range1 = jobFilterService.parseSalaryRange('15-25K');
console.log(range1); // { min: 15000, max: 25000 }
const range2 = jobFilterService.parseSalaryRange('2-3万');
console.log(range2); // { min: 20000, max: 30000 }
```
---
### 7. parseExpectedSalary(expectedSalary)
解析期望薪资字符串,返回平均值。
**参数:**
- `expectedSalary` (string): 期望薪资描述字符串
**返回值:**
- `number`: 期望薪资数值(元),如果是范围则返回平均值
- `null`: 解析失败时
**使用示例:**
```javascript
const salary1 = jobFilterService.parseExpectedSalary('20-30K');
console.log(salary1); // 25000平均值
const salary2 = jobFilterService.parseExpectedSalary('25K');
console.log(salary2); // 25000
```
---
### 8. parseWorkYears(workYearsStr)
解析工作年限字符串为数字。
**参数:**
- `workYearsStr` (string): 工作年限字符串(如 "3年"、"5年以上"
**返回值:**
- `number`: 工作年限数字
- `null`: 解析失败时
**使用示例:**
```javascript
const years = jobFilterService.parseWorkYears('3-5年');
console.log(years); // 3提取第一个数字
```
---
## 辅助方法
### buildJobText(jobInfo)
构建职位文本(用于匹配)。
**参数:**
- `jobInfo` (object): 职位信息对象
**返回值:**
- `string`: 合并后的职位文本(小写)
---
### buildResumeText(resumeInfo)
构建简历文本(用于匹配)。
**参数:**
- `resumeInfo` (object): 简历信息对象
**返回值:**
- `string`: 合并后的简历文本(小写)
---
### calculateSkillMatch(jobText, resumeText, commonSkills)
计算技能匹配度0-100分
**参数:**
- `jobText` (string): 职位文本
- `resumeText` (string): 简历文本
- `commonSkills` (Array<string>): 技能关键词列表
**返回值:**
- `number`: 匹配度分数0-100
---
### calculateExperienceMatch(jobInfo, resumeInfo)
计算经验匹配度0-100分
**参数:**
- `jobInfo` (object): 职位信息
- `resumeInfo` (object): 简历信息
**返回值:**
- `number`: 匹配度分数0-100
---
### calculateSalaryMatch(jobInfo, resumeInfo)
计算薪资合理性0-100分
**参数:**
- `jobInfo` (object): 职位信息
- `resumeInfo` (object): 简历信息
**返回值:**
- `number`: 匹配度分数0-100
---
### checkOutsourcing(jobText)
检查是否为外包岗位。
**参数:**
- `jobText` (string): 职位文本
**返回值:**
- `boolean`: 是否为外包
---
## 默认配置
### 默认技能关键词
```javascript
[
'Vue', 'React', 'Angular', 'JavaScript', 'TypeScript', 'Node.js',
'Python', 'Java', 'C#', '.NET', 'Flutter', 'React Native',
'Webpack', 'Vite', 'Redux', 'MobX', 'Express', 'Koa',
'Django', 'Flask', 'MySQL', 'MongoDB', 'Redis',
'WebRTC', 'FFmpeg', 'Canvas', 'WebSocket', 'HTML5', 'CSS3',
'jQuery', 'Bootstrap', 'Element UI', 'Ant Design',
'Git', 'Docker', 'Kubernetes', 'AWS', 'Azure',
'Selenium', 'Jest', 'Mocha', 'Cypress'
]
```
### 默认排除关键词
```javascript
[
'外包', '外派', '驻场', '销售', '客服', '电话销售',
'地推', '推广', '市场', '运营', '行政', '文员'
]
```
## 数据库依赖
该服务依赖以下数据库表:
### job_types 表
存储职位类型配置,需要包含以下字段:
- `id` (number): 职位类型ID
- `is_enabled` (number): 是否启用1=启用0=禁用)
- `commonSkills` (string|JSON): 技能关键词列表JSON数组字符串
- `excludeKeywords` (string|JSON): 排除关键词列表JSON数组字符串
**示例数据:**
```json
{
"id": 1,
"is_enabled": 1,
"commonSkills": "[\"Vue\", \"React\", \"JavaScript\"]",
"excludeKeywords": "[\"外包\", \"外派\"]"
}
```
## 使用示例
### 完整使用流程
```javascript
const jobFilterService = require('./job_filter_service');
// 1. 分析单个职位的匹配度
const jobInfo = {
jobTitle: '高级前端开发工程师',
companyName: 'XX科技有限公司',
description: '负责前端架构设计要求5年以上Vue/React开发经验',
skills: 'Vue, React, TypeScript, Node.js',
requirements: '本科及以上学历,有大型项目经验',
salary: '25-40K·14薪'
};
const resumeInfo = {
skills: 'Vue, React, JavaScript, TypeScript, Node.js, Webpack',
skillDescription: '精通Vue生态熟悉React有5年+前端开发经验',
currentPosition: '高级前端开发工程师',
expectedPosition: '前端架构师',
workYears: 6,
expectedSalary: '30K',
education: '本科'
};
const analysis = await jobFilterService.analyzeJobMatch(
jobInfo,
resumeInfo,
1 // 职位类型ID
);
console.log('匹配分析结果:');
console.log('综合评分:', analysis.overallScore);
console.log('技能匹配度:', analysis.skillMatch);
console.log('经验匹配度:', analysis.experienceMatch);
console.log('薪资匹配度:', analysis.salaryMatch);
console.log('是否外包:', analysis.isOutsourcing);
console.log('匹配原因:', analysis.matchReasons);
console.log('关注点:', analysis.concerns);
console.log('投递建议:', analysis.suggestion);
// 2. 过滤职位列表
const jobs = await Job.findAll({ where: { status: 1 } });
const filteredJobs = await jobFilterService.filterJobs(
jobs,
{
minScore: 70,
excludeOutsourcing: true,
excludeKeywords: ['销售', '客服']
},
resumeInfo,
1
);
console.log(`共找到 ${filteredJobs.length} 个匹配的职位`);
filteredJobs.forEach((job, index) => {
console.log(`${index + 1}. ${job.jobTitle} - 匹配分数:${job.matchScore}`);
});
// 3. 自定义权重评分
const accountConfig = {
user_longitude: 116.4074,
user_latitude: 39.9042
};
const priorityWeights = [
{ key: 'distance', weight: 25 },
{ key: 'salary', weight: 35 },
{ key: 'work_years', weight: 25 },
{ key: 'education', weight: 15 }
];
const scoreResult = jobFilterService.calculateJobScoreWithWeights(
jobInfo,
resumeInfo,
accountConfig,
{ commonSkills: ['Vue', 'React', 'TypeScript'] },
priorityWeights
);
console.log('自定义权重评分:', scoreResult.totalScore);
console.log('各项评分:', scoreResult.scores);
```
## 注意事项
1. **缓存机制**职位类型配置会缓存5分钟修改数据库配置后需要调用 `clearCache()` 清除缓存
2. **性能优化**
- 大量职位过滤时,建议分批处理
- 避免在循环中频繁调用 `getJobTypeConfig()`,配置会被自动缓存
3. **文本匹配**
- 所有文本匹配均为小写匹配,不区分大小写
- 匹配逻辑较为简单,如需更精确的匹配,建议使用 AI 分析(二期规划)
4. **薪资解析**
- 支持多种薪资格式,但可能无法解析所有格式
- 解析失败时返回默认分数或 null
5. **错误处理**
- 所有方法都包含错误处理,失败时返回默认值或空结果
- 建议在生产环境中监控日志输出
## 版本历史
- **v1.0.0**: 初始版本,支持基础文本匹配和过滤功能
- 支持从数据库动态获取职位类型配置
- 支持自定义权重评分计算

View File

@@ -6,18 +6,7 @@ const dayjs = require('dayjs');
*/
class ScheduleConfig {
constructor() {
// 工作时间配置
this.workHours = {
start: 6,
end: 23
};
// 频率限制配置(毫秒)
this.rateLimits = {
search: 30 * 60 * 1000, // 搜索间隔30分钟
apply: 5 * 60 * 1000, // 投递间隔5分钟
chat: 1 * 60 * 1000, // 聊天间隔1分钟
};
// 单日操作限制
this.dailyLimits = {
@@ -62,15 +51,6 @@ class ScheduleConfig {
};
}
/**
* 检查是否在工作时间
* @returns {boolean}
*/
isWorkingHours() {
const now = dayjs();
const hour = now.hour();
return hour >= this.workHours.start && hour < this.workHours.end;
}
/**
* 获取任务超时时间
@@ -97,15 +77,6 @@ class ScheduleConfig {
return priority;
}
/**
* 获取操作频率限制
* @param {string} operation - 操作类型
* @returns {number} 间隔时间(毫秒)
*/
getRateLimit(operation) {
return this.rateLimits[operation] || 0;
}
/**
* 获取日限制
* @param {string} operation - 操作类型

View File

@@ -100,19 +100,8 @@ class DeviceManager {
* 检查是否可以执行操作
*/
canExecuteOperation(sn_code, operation_type) {
// 检查频率限制
// 检查日限制(频率限制已由各任务使用账号配置中的间隔时间,不再使用全局配置)
const device = this.devices.get(sn_code);
if (device) {
const lastTime = device[`last${operation_type.charAt(0).toUpperCase() + operation_type.slice(1)}`] || 0;
const interval = config.getRateLimit(operation_type);
if (Date.now() - lastTime < interval) {
return { allowed: false, reason: '操作过于频繁' };
}
}
// 检查日限制
if (device && device.dailyCounts) {
const today = utils.getTodayString();
if (device.dailyCounts.date !== today) {

View File

@@ -157,6 +157,31 @@ module.exports = (db) => {
allowNull: true,
defaultValue: ''
},
// 文本匹配分析结果
textMatchScore: {
comment: '文本匹配综合评分',
type: Sequelize.INTEGER,
allowNull: true,
defaultValue: 0
},
matchSuggestion: {
comment: '投递建议',
type: Sequelize.STRING(200),
allowNull: true,
defaultValue: ''
},
matchConcerns: {
comment: '关注点(JSON数组)',
type: Sequelize.TEXT,
allowNull: true,
defaultValue: '[]'
},
textMatchAnalysis: {
comment: '文本匹配详细分析结果(JSON)',
type: Sequelize.TEXT,
allowNull: true,
defaultValue: ''
},
outsourcingAnalysis: {
comment: '外包识别分析结果',
type: Sequelize.TEXT,