This commit is contained in:
张成
2025-12-30 16:18:28 +08:00
parent fa2dea3f04
commit c45ea21c83
14 changed files with 64 additions and 984 deletions

View File

@@ -1,307 +0,0 @@
const axios = require('axios');
const config = require('../../../config/config');
const logs = require('../logProxy');
/**
* Qwen 2.5 大模型服务
* 集成阿里云 DashScope API提供智能化的岗位筛选、聊天生成、简历分析等功能
*/
class aiService {
constructor() {
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;
}
/**
* 调用 Qwen 2.5 API
* @param {string} prompt - 提示词
* @param {object} options - 配置选项
* @returns {Promise<object>} API响应结果
*/
async callAPI(prompt, options = {}) {
const requestData = {
model: this.model,
messages: [
{
role: 'system',
content: options.systemPrompt || '你是一个专业的招聘顾问,擅长分析岗位信息、生成聊天内容和分析简历匹配度。'
},
{
role: 'user',
content: prompt
}
],
temperature: options.temperature || 0.7,
max_tokens: options.maxTokens || 2000,
top_p: options.topP || 0.9
};
for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
try {
const response = await axios.post(this.apiUrl, requestData, {
headers: {
'Authorization': `Bearer ${this.apiKey}`, // DashScope 使用 Bearer token 格式
'Content-Type': 'application/json'
},
timeout: 30000
});
return {
data: response.data,
content: response.data.choices?.[0]?.message?.content || ''
};
} catch (error) {
console.log(`Qwen 2.5 API调用失败 (尝试 ${attempt}/${this.maxRetries}): ${error.message}`);
if (attempt === this.maxRetries) {
throw new Error(error.message);
}
// 等待后重试
await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
}
}
}
/**
* 岗位智能筛选
* @param {object} jobInfo - 岗位信息
* @param {object} resumeInfo - 简历信息
* @returns {Promise<object>} 筛选结果
*/
async analyzeJob(jobInfo, resumeInfo) {
const prompt = `
请分析以下岗位信息,并给出详细的评估结果:
岗位信息:
- 公司名称:${jobInfo.companyName || '未知'}
- 职位名称:${jobInfo.jobTitle || '未知'}
- 薪资范围:${jobInfo.salary || '未知'}
- 工作地点:${jobInfo.location || '未知'}
- 岗位描述:${jobInfo.description || '未知'}
- 技能要求:${jobInfo.skills || '未知'}
简历信息:
- 技能标签:${resumeInfo.skills || '未知'}
- 工作经验:${resumeInfo.experience || '未知'}
- 教育背景:${resumeInfo.education || '未知'}
- 期望薪资:${resumeInfo.expectedSalary || '未知'}
请从以下维度进行分析:
1. 技能匹配度0-100分
2. 经验匹配度0-100分
3. 薪资合理性0-100分
4. 公司质量评估0-100分
5. 是否为外包岗位(是/否)
6. 综合推荐指数0-100分
7. 详细分析说明
8. 投递建议
请以JSON格式返回结果。
`;
const result = await this.callAPI(prompt, {
systemPrompt: '你是一个专业的招聘分析师,擅长评估岗位与简历的匹配度。请提供客观、专业的分析结果。',
temperature: 0.3
});
try {
// 尝试解析JSON响应
const analysis = JSON.parse(result.content);
return {
analysis: analysis
};
} catch (parseError) {
// 如果解析失败,返回原始内容
return {
analysis: {
content: result.content,
parseError: true
}
};
}
}
/**
* 生成个性化聊天内容
* @param {object} jobInfo - 岗位信息
* @param {object} resumeInfo - 简历信息
* @param {string} chatType - 聊天类型 (greeting/interview/followup)
* @returns {Promise<object>} 聊天内容
*/
async generateChatContent(jobInfo, resumeInfo, chatType = 'greeting') {
const chatTypeMap = {
'greeting': '初次打招呼',
'interview': '面试邀约',
'followup': '跟进沟通'
};
const prompt = `
请为以下场景生成个性化的聊天内容:
聊天类型:${chatTypeMap[chatType] || chatType}
岗位信息:
- 公司名称:${jobInfo.companyName || '未知'}
- 职位名称:${jobInfo.jobTitle || '未知'}
- 技能要求:${jobInfo.skills || '未知'}
简历信息:
- 技能标签:${resumeInfo.skills || '未知'}
- 工作经验:${resumeInfo.experience || '未知'}
- 项目经验:${resumeInfo.projects || '未知'}
要求:
1. 内容要自然、专业、个性化
2. 突出简历与岗位的匹配点
3. 避免过于机械化的表达
4. 长度控制在100-200字
5. 体现求职者的诚意和热情
请直接返回聊天内容,不需要其他格式。
`;
const result = await this.callAPI(prompt, {
systemPrompt: '你是一个专业的招聘沟通专家,擅长生成自然、专业的求职聊天内容。',
temperature: 0.8
});
return result;
}
/**
* 分析简历要素
* @param {string} resumeText - 简历文本内容
* @returns {Promise<object>} 简历分析结果
*/
async analyzeResume(resumeText) {
const prompt = `
请分析以下简历内容,并返回 JSON 格式的分析结果:
简历内容:
${resumeText}
请按以下格式返回 JSON 结果:
{
"skillTags": ["技能1", "技能2", "技能3"], // 技能标签数组(编程语言、框架、工具等)
"strengths": "核心优势描述", // 简历的优势和亮点
"weaknesses": "不足之处描述", // 简历的不足或需要改进的地方
"careerSuggestion": "职业发展建议", // 针对该简历的职业发展方向和建议
"competitiveness": 75 // 竞争力评分0-100的整数综合考虑工作年限、技能、经验等因素
}
要求:
1. skillTags 必须是字符串数组
2. strengths、weaknesses、careerSuggestion 是字符串描述
3. competitiveness 必须是 0-100 之间的整数
4. 所有字段都必须返回,如果没有相关信息,使用空数组或空字符串
`;
const result = await this.callAPI(prompt, {
systemPrompt: '你是一个专业的简历分析师,擅长分析简历的核心要素、优势劣势、竞争力评分和职业发展建议。请以 JSON 格式返回分析结果,确保格式正确。',
temperature: 0.3,
maxTokens: 1500
});
try {
// 尝试从返回内容中提取 JSON
let content = result.content.trim();
// 如果返回内容被代码块包裹,提取其中的 JSON
const jsonMatch = content.match(/```(?:json)?\s*(\{[\s\S]*\})\s*```/) || content.match(/(\{[\s\S]*\})/);
if (jsonMatch) {
content = jsonMatch[1];
}
const analysis = JSON.parse(content);
return {
analysis: analysis
};
} catch (parseError) {
console.error(`[AI服务] 简历分析结果解析失败:`, parseError);
console.error(`[AI服务] 原始内容:`, result.content);
return {
analysis: {
content: result.content,
parseError: true
}
};
}
}
/**
* 生成面试邀约内容
* @param {object} jobInfo - 岗位信息
* @param {object} chatHistory - 聊天历史
* @returns {Promise<object>} 面试邀约内容
*/
async generateInterviewInvitation(jobInfo, chatHistory) {
const prompt = `
请基于以下信息生成面试邀约内容:
岗位信息:
- 公司名称:${jobInfo.companyName || '未知'}
- 职位名称:${jobInfo.jobTitle || '未知'}
- 工作地点:${jobInfo.location || '未知'}
聊天历史:
${chatHistory || '无'}
要求:
1. 表达面试邀约的诚意
2. 提供灵活的时间选择
3. 说明面试形式和地点
4. 体现对候选人的重视
5. 语言自然、专业
请直接返回面试邀约内容。
`;
const result = await this.callAPI(prompt, {
systemPrompt: '你是一个专业的HR擅长生成面试邀约内容。',
temperature: 0.6
});
return result;
}
/**
* 识别外包岗位
* @param {object} jobInfo - 岗位信息
* @returns {Promise<object>} 外包识别结果
*/
async identifyOutsourcingJob(jobInfo) {
const prompt = `
请分析以下岗位信息,判断是否为外包岗位:
岗位信息:
- 公司名称:${jobInfo.companyName || '未知'}
- 职位名称:${jobInfo.jobTitle || '未知'}
- 岗位描述:${jobInfo.description || '未知'}
- 技能要求:${jobInfo.skills || '未知'}
- 工作地点:${jobInfo.location || '未知'}
外包岗位特征:
1. 公司名称包含"外包"、"派遣"、"人力"等关键词
2. 岗位描述提到"项目外包"、"驻场开发"等
3. 技能要求过于宽泛或具体
4. 工作地点频繁变动
5. 薪资结构不明确
请判断是否为外包岗位,并给出详细分析。
`;
const result = await this.callAPI(prompt, {
systemPrompt: '你是一个专业的岗位分析师,擅长识别外包岗位的特征。',
temperature: 0.3
});
return result;
}
}
module.exports = new aiService();

View File

@@ -3,10 +3,7 @@
* 聚合所有 job 相关模块的方法,提供统一的对外接口
*/
const jobManager = require('./jobManager');
const resumeManager = require('./resumeManager');
const chatManager = require('./chatManager');
const { jobManager, resumeManager, chatManager } = require('./managers');
const pack = (instance) => {
const proto = Object.getPrototypeOf(instance);
@@ -23,7 +20,6 @@ const pack = (instance) => {
/**
* 便捷方法:直接导出常用方法
* 使用下划线命名规范
*/
module.exports = {
...pack(jobManager),

View File

@@ -1,651 +0,0 @@
# 职位过滤服务文档
## 概述
`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

@@ -1,5 +1,8 @@
// const aiService = require('./aiService'); // 二期规划AI 服务暂时禁用
const logs = require('../logProxy');
const aiServiceModule = require('../../../services/ai_service');
const logs = require('../../logProxy');
// 实例化AI服务
const aiService = aiServiceModule.getInstance();
/**
* 智能聊天管理模块

View File

@@ -0,0 +1,13 @@
/**
* Managers 模块统一导出
*/
const jobManager = require('./jobManager');
const resumeManager = require('./resumeManager');
const chatManager = require('./chatManager');
module.exports = {
jobManager,
resumeManager,
chatManager
};

View File

@@ -1,10 +1,13 @@
// const aiService = require('./aiService'); // 二期规划AI 服务暂时禁用
const jobFilterService = require('./job_filter_service'); // 使用文本匹配过滤服务
const locationService = require('../../services/location_service'); // 位置服务
const logs = require('../logProxy');
const db = require('../dbProxy');
const aiServiceModule = require('../../../services/ai_service');
const { jobFilterService } = require('../services');
const locationService = require('../../../services/locationService');
const logs = require('../../logProxy');
const db = require('../../dbProxy');
const { v4: uuidv4 } = require('uuid');
// 实例化AI服务
const aiService = aiServiceModule.getInstance();
/**
* 工作管理模块
* 负责简历获取分析存储和匹配度计算

View File

@@ -1,9 +1,12 @@
const aiService = require('./aiService');
const jobFilterService = require('./job_filter_service');
const logs = require('../logProxy');
const db = require('../dbProxy');
const aiServiceModule = require('../../../services/ai_service');
const { jobFilterService } = require('../services');
const logs = require('../../logProxy');
const db = require('../../dbProxy');
const { v4: uuidv4 } = require('uuid');
// 实例化AI服务
const aiService = aiServiceModule.getInstance();
/**
* 简历管理模块
* 负责简历获取分析存储和匹配度计算

View File

@@ -0,0 +1,9 @@
/**
* Services 模块统一导出
*/
const jobFilterService = require('./jobFilterService');
module.exports = {
jobFilterService
};

View File

@@ -4,8 +4,8 @@
* 支持从数据库动态获取职位类型的技能关键词和排除关键词
*/
const db = require('../dbProxy.js');
const locationService = require('../../services/location_service');
const db = require('../../dbProxy.js');
const locationService = require('../../../services/locationService');
class JobFilterService {
constructor() {

View File

@@ -0,0 +1,7 @@
/**
* Utils 模块统一导出
*/
module.exports = {
// 工具函数将在需要时添加
};

View File

@@ -1,4 +1,4 @@
const db = require('../dbProxy');
const db = require('../../dbProxy');
/**
* 统一错误处理模块

View File

@@ -3,7 +3,7 @@
* 负责向客户端推送设备当前工作状态(任务、指令等)
*/
const db = require('../dbProxy');
const db = require('../../dbProxy');
class DeviceWorkStatusNotifier {
constructor() {

View File

@@ -4,13 +4,14 @@
*/
const axios = require('axios');
const aiConfig = require('./config/aiConfig');
class AIService {
constructor(config = {}) {
this.apiKey = config.apiKey || process.env.AI_API_KEY || '';
this.baseURL = config.baseURL || process.env.AI_BASE_URL || 'https://api.deepseek.com';
this.model = config.model || 'deepseek-chat';
this.timeout = config.timeout || 30000;
constructor() {
this.apiKey = aiConfig.apiKey;
this.baseURL = aiConfig.baseURL;
this.model = aiConfig.model;
this.timeout = aiConfig.timeout;
// 创建axios实例
this.client = axios.create({