Files
autoAiWorkSys/api/middleware/job/jobManager.js
张成 5d7444cd65 1
2025-11-24 13:23:42 +08:00

876 lines
32 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// const aiService = require('./aiService'); // 二期规划AI 服务暂时禁用
const jobFilterService = require('./job_filter_service'); // 使用文本匹配过滤服务
const locationService = require('../../services/locationService'); // 位置服务
const logs = require('../logProxy');
const db = require('../dbProxy');
const { v4: uuidv4 } = require('uuid');
/**
* 工作管理模块
* 负责简历获取、分析、存储和匹配度计算
*/
class JobManager {
constructor() {
}
// 启动客户端那个平台 用户信息,心跳机制
async set_user_info(sn_code, mqttClient, user_info) {
const response = await mqttClient.publishAndWait(sn_code, {
platform: platform,
action: 'set_user_info',
data: {
user_info: user_info
}
});
}
/**
* 获取登录二维码
* @param {string} sn_code - 设备SN码
* @param {object} mqttClient - MQTT客户端
* @param {object} params - 参数对象
* @returns {Promise<object>} 二维码信息
*/
async get_login_qr_code(sn_code, mqttClient, params = {}) {
console.log(`[工作管理] 开始获取设备 ${sn_code} 的登录二维码`);
const { platform = 'boss' } = params;
// 通过MQTT指令获取登录二维码
const response = await mqttClient.publishAndWait(sn_code, {
platform: platform,
action: 'get_login_qr_code',
data: {}
});
if (!response || response.code !== 200) {
console.error(`[工作管理] 获取登录二维码失败:`, response);
throw new Error(response?.message || '获取登录二维码失败');
}
const qrCodeData = response.data;
console.log(`[工作管理] 成功获取登录二维码数据:`, qrCodeData);
return qrCodeData;
}
/**
* 打开机器人检测测试页
* @param {string} sn_code - 设备SN码
* @param {object} mqttClient - MQTT客户端
* @param {object} params - 参数对象
* @returns {Promise<object>} 执行结果
*/
async open_bot_detection(sn_code, mqttClient, params = {}) {
console.log(`[工作管理] 开始打开设备 ${sn_code} 的机器人检测测试页`);
const { platform = 'boss' } = params;
// 通过MQTT指令打开机器人检测测试页
const response = await mqttClient.publishAndWait(sn_code, {
platform: platform,
action: 'open_bot_detection',
data: {}
});
if (!response || response.code !== 200) {
console.error(`[工作管理] 打开机器人检测测试页失败:`, response);
throw new Error(response?.message || '打开机器人检测测试页失败');
}
const result = response.data;
console.log(`[工作管理] 成功打开机器人检测测试页:`, result);
return result;
}
/**
* 获取用户信息
* @param {string} sn_code - 设备SN码
* @param {object} mqttClient - MQTT客户端
* @param {object} params - 参数对象
* @returns {Promise<object>} 用户信息
*/
async get_user_info(sn_code, mqttClient, params = {}) {
console.log(`[工作管理] 开始获取设备 ${sn_code} 的用户信息`);
const { platform = 'boss' } = params;
// 通过MQTT指令获取用户信息
const response = await mqttClient.publishAndWait(sn_code, {
platform: platform,
action: 'get_user_info',
data: {}
});
if (!response || response.code !== 200) {
console.error(`[工作管理] 获取用户信息失败:`, response);
throw new Error(response?.message || '获取用户信息失败');
}
const userInfo = response.data;
console.log(`[工作管理] 成功获取用户信息:`, userInfo);
return userInfo;
}
/**
* 搜索岗位
* @param {string} sn_code - 设备SN码
* @param {object} mqttClient - MQTT客户端
* @param {object} params - 参数
* @returns {Promise<object>} 搜索结果
*/
async search_jobs(sn_code, mqttClient, params = {}) {
const { keyword = '前端', platform = 'boss' } = params;
console.log(`[工作管理] 开始搜索设备 ${sn_code} 的岗位,关键词: ${keyword}`);
// 通过MQTT指令搜索岗位
const response = await mqttClient.publishAndWait(sn_code, {
platform,
action: "search_jobs",
data: { keyword }
});
if (!response || response.code !== 200) {
console.error(`[工作管理] 搜索岗位失败:`, response);
throw new Error('搜索岗位失败');
}
console.log(`[工作管理] 成功搜索岗位`);
return response.data;
}
/**
* 获取岗位列表
* @param {string} sn_code - 设备SN码
* @param {object} mqttClient - MQTT客户端
* @param {object} params - 参数
* @returns {Promise<object>} 岗位列表
*/
async get_job_list(sn_code, mqttClient, params = {}) {
const { keyword = '前端', platform = 'boss', pageCount = 3 } = params;
console.log(`[工作管理] 开始获取设备 ${sn_code} 的岗位列表,关键词: ${keyword}`);
// 通过MQTT指令获取岗位列表
const response = await mqttClient.publishAndWait(sn_code, {
platform,
action: "get_job_list",
data: { keyword, pageCount }
});
if (!response || response.code !== 200) {
console.error(`[工作管理] 获取岗位列表失败:`, response);
throw new Error('获取岗位列表失败');
}
// 处理职位列表数据response.data 可能是数组(职位列表.json 格式)或单个对象
let jobs = [];
if (Array.isArray(response.data)) {
// 如果是数组格式(职位列表.json遍历每个元素提取岗位数据
for (const item of response.data) {
if (item.data?.zpData?.jobList && Array.isArray(item.data.zpData.jobList)) {
jobs = jobs.concat(item.data.zpData.jobList);
}
}
console.log(`[工作管理] 从 ${response.data.length} 个响应中提取岗位数据`);
} else if (response.data?.data?.zpData?.jobList) {
// 如果是单个对象格式,从 data.zpData.jobList 获取
jobs = response.data.data.zpData.jobList || [];
} else if (response.data?.zpData?.jobList) {
// 兼容旧格式:直接从 zpData.jobList 获取
jobs = response.data.zpData.jobList || [];
}
console.log(`[工作管理] 成功获取岗位数据,共 ${jobs.length} 个岗位`);
// 保存职位到数据库
try {
await this.saveJobsToDatabase(sn_code, platform, keyword, jobs);
} catch (error) {
console.error(`[工作管理] 保存职位到数据库失败:`, error);
// 不影响主流程,继续返回数据
}
const result = {
jobs: jobs,
keyword: keyword,
platform: platform,
count: jobs.length
};
return result;
}
/**
* 保存职位列表到数据库
* @param {string} sn_code - 设备SN码
* @param {string} platform - 平台
* @param {string} keyword - 搜索关键词
* @param {Array} jobs - 职位列表
*/
async saveJobsToDatabase(sn_code, platform, keyword, jobs) {
const job_postings = db.getModel('job_postings');
console.log(`[工作管理] 开始保存 ${jobs.length} 个职位到数据库`);
for (const job of jobs) {
try {
// 构建职位信息对象
const jobInfo = {
sn_code,
platform,
keyword,
// Boss直聘字段映射
encryptBossId: job.encryptBossId || '',
jobId: job.encryptJobId || '',
jobTitle: job.jobName || '',
companyId: job.encryptBrandId || '',
companyName: job.brandName || '',
companySize: job.brandScaleName || '',
companyIndustry: job.brandIndustry || '',
salary: job.salaryDesc || '',
// 岗位要求(从 jobLabels 和 skills 提取)
jobRequirements: JSON.stringify({
experience: job.jobExperience || '',
education: job.jobDegree || '',
labels: job.jobLabels || [],
skills: job.skills || []
}),
// 工作地点
location: [job.cityName, job.areaDistrict, job.businessDistrict]
.filter(Boolean).join(' '),
experience: job.jobExperience || '',
education: job.jobDegree || '',
// 原始数据
originalData: JSON.stringify(job),
// 默认状态
applyStatus: 'pending',
chatStatus: 'none'
};
// 调用位置服务解析 location + companyName 获取坐标
if (jobInfo.location && jobInfo.companyName) {
const addressToParse = `${jobInfo.location.replaceAll(' ', '')}${jobInfo.companyName}`;
// 等待 1秒
await new Promise(resolve => setTimeout(resolve, 1000));
const location = await locationService.getLocationByAddress(addressToParse).catch(error => {
console.error(`[工作管理] 获取位置失败:`, error);
});
if (location) {
jobInfo.latitude = String(location.lat);
jobInfo.longitude = String(location.lng);
}
}
// 检查是否已存在(根据 jobId 和 sn_code
const existingJob = await job_postings.findOne({
where: {
jobId: jobInfo.jobId,
sn_code: sn_code
}
});
if (existingJob) {
// 更新现有职位
await job_postings.update(jobInfo, {
where: {
jobId: jobInfo.jobId,
sn_code: sn_code
}
});
console.log(`[工作管理] 职位已更新 - ${jobInfo.jobTitle} @ ${jobInfo.companyName}`);
} else {
// 创建新职位
await job_postings.create(jobInfo);
console.log(`[工作管理] 职位已创建 - ${jobInfo.jobTitle} @ ${jobInfo.companyName}`);
}
} catch (error) {
console.error(`[工作管理] 保存职位失败:`, error, job);
// 继续处理下一个职位
}
}
console.log(`[工作管理] 职位保存完成`);
}
/**
* 投递简历(单个职位)
* @param {string} sn_code - 设备SN码
* @param {object} mqttClient - MQTT客户端
* @param {object} params - 参数
* @param {string} params.jobId - 职位ID必需
* @param {string} params.platform - 平台默认boss
* @param {string} params.encryptBossId - 加密的Boss ID可选
* @param {string} params.securityId - 安全ID可选
* @param {string} params.brandName - 公司名称(可选)
* @param {string} params.jobTitle - 职位标题(可选)
* @param {string} params.companyName - 公司名称(可选)
* @returns {Promise<object>} 投递结果
*/
async applyJob(sn_code, mqttClient, params = {}) {
const { platform = 'boss', jobId, encryptBossId, securityId, brandName, jobTitle, companyName } = params;
if (!jobId) {
throw new Error('jobId 参数不能为空请指定要投递的职位ID');
}
console.log(`[工作管理] 开始投递单个职位,设备: ${sn_code}, 职位: ${jobTitle || jobId}`);
const job_postings = db.getModel('job_postings');
const apply_records = db.getModel('apply_records');
try {
// 从数据库获取职位信息
const where = { sn_code, jobId, platform };
if (encryptBossId) where.encryptBossId = encryptBossId;
const job = await job_postings.findOne({ where });
if (!job) {
throw new Error(`未找到职位记录: ${jobId}`);
}
const jobData = job.toJSON();
// 检查是否已存在投递记录(避免重复投递同一职位)
const existingApply = await apply_records.findOne({ where: { sn_code, jobId: jobData.jobId } });
if (existingApply) {
console.log(`[工作管理] 跳过已投递职位: ${jobData.jobTitle} @ ${jobData.companyName}`);
return {
success: false,
deliveredCount: 0,
failedCount: 1,
message: '该岗位已投递过',
deliveredJobs: [],
failedJobs: [{
jobId: jobData.jobId,
jobTitle: jobData.jobTitle,
companyName: jobData.companyName,
error: '该岗位已投递过'
}]
};
}
// 检查该公司是否在一个月内已投递过(避免连续投递同一公司)
const oneMonthAgo = new Date();
oneMonthAgo.setMonth(oneMonthAgo.getMonth() - 1);
const Sequelize = require('sequelize');
const recentCompanyApply = await apply_records.findOne({
where: {
sn_code: sn_code,
companyName: jobData.companyName,
applyTime: {
[Sequelize.Op.gte]: oneMonthAgo
}
},
order: [['applyTime', 'DESC']]
});
if (recentCompanyApply) {
const daysAgo = Math.floor((new Date() - new Date(recentCompanyApply.applyTime)) / (1000 * 60 * 60 * 24));
console.log(`[工作管理] 跳过一个月内已投递的公司: ${jobData.companyName} (${daysAgo}天前投递过)`);
return {
success: false,
deliveredCount: 0,
failedCount: 1,
message: `该公司在${daysAgo}天前已投递过,一个月内不重复投递`,
deliveredJobs: [],
failedJobs: [{
jobId: jobData.jobId,
jobTitle: jobData.jobTitle,
companyName: jobData.companyName,
error: `该公司在${daysAgo}天前已投递过,一个月内不重复投递`
}]
};
}
console.log(`[工作管理] 投递职位: ${jobData.jobTitle} @ ${jobData.companyName}`);
// 通过MQTT指令投递简历
const response = await mqttClient.publishAndWait(sn_code, {
platform,
action: "deliver_resume",
data: {
encryptJobId: jobData.jobId,
securityId: jobData.securityId || securityId || '',
brandName: jobData.companyName || brandName || ''
}
});
if (response && response.code === 200) {
// 投递成功
await job_postings.update(
{ applyStatus: 'applied', applyTime: new Date() },
{ where: { id: jobData.id } }
);
// 计算距离和获取评分信息
let distance = null;
let locationScore = jobData.scoreDetails?.locationScore || 0;
try {
// 获取账号的经纬度
const pla_account = db.getModel('pla_account');
const account = await pla_account.findOne({
where: { sn_code, platform_type: platform }
});
// 如果账号和职位都有经纬度,计算实际距离
if (account && account.user_latitude && account.user_longitude &&
jobData.latitude && jobData.longitude) {
const userLat = parseFloat(account.user_latitude);
const userLng = parseFloat(account.user_longitude);
const jobLat = parseFloat(jobData.latitude);
const jobLng = parseFloat(jobData.longitude);
if (!isNaN(userLat) && !isNaN(userLng) && !isNaN(jobLat) && !isNaN(jobLng)) {
distance = locationService.calculateDistance(userLat, userLng, jobLat, jobLng);
console.log(`[工作管理] 计算距离: ${distance} 公里`);
}
}
} catch (error) {
console.warn(`[工作管理] 计算距离失败:`, error.message);
// 距离计算失败不影响投递记录保存
}
await apply_records.create({
sn_code: sn_code,
platform: platform,
jobId: jobData.jobId,
encryptBossId: jobData.encryptBossId || '',
jobTitle: jobData.jobTitle,
companyName: jobData.companyName,
companyId: jobData.companyId || '',
salary: jobData.salary || '',
location: jobData.location || '',
resumeId: jobData.resumeId || '',
resumeName: jobData.resumeName || '',
matchScore: jobData.matchScore || jobData.scoreDetails?.totalScore || 0,
isOutsourcing: jobData.isOutsourcing || false,
priority: jobData.priority || 5,
isAutoApply: true,
applyStatus: 'success',
applyTime: new Date(),
feedbackStatus: 'none',
taskId: jobData.taskId || '',
keyword: jobData.keyword || '',
originalData: JSON.stringify({
...(response.data || {}),
scoreDetails: {
...(jobData.scoreDetails || {}),
locationScore: locationScore,
distance: distance // 实际距离(公里)
}
})
});
console.log(`[工作管理] 投递成功: ${jobData.jobTitle}`);
return {
success: true,
deliveredCount: 1,
failedCount: 0,
deliveredJobs: [{
jobId: jobData.jobId,
jobTitle: jobData.jobTitle,
companyName: jobData.companyName
}],
failedJobs: []
};
} else {
// 投递失败
await job_postings.update(
{ applyStatus: 'failed' },
{ where: { id: jobData.id } }
);
await apply_records.create({
sn_code: sn_code,
platform: platform,
jobId: jobData.jobId,
encryptBossId: jobData.encryptBossId || '',
jobTitle: jobData.jobTitle,
companyName: jobData.companyName,
companyId: jobData.companyId || '',
salary: jobData.salary || '',
location: jobData.location || '',
resumeId: jobData.resumeId || '',
resumeName: jobData.resumeName || '',
matchScore: jobData.matchScore || 0,
isOutsourcing: jobData.isOutsourcing || false,
priority: jobData.priority || 5,
isAutoApply: true,
applyStatus: 'failed',
applyTime: new Date(),
feedbackStatus: 'none',
taskId: jobData.taskId || '',
keyword: jobData.keyword || '',
errorMessage: response?.message || '投递失败',
originalData: JSON.stringify(response || {})
});
throw new Error(response?.message || '投递失败');
}
} catch (error) {
console.error(`[工作管理] 投递异常: ${jobTitle || jobId}`, error);
throw error;
}
}
/**
* 根据规则过滤职位并评分
* 评分规则距离30% + 关键字10% + 薪资20% + 公司规模20% = 总分
* 只有总分 >= 60 的职位才会被投递
* @param {Array} jobs - 职位列表
* @param {Object} filterRules - 过滤规则(包含账号配置和简历信息)
* @returns {Promise<Array>} 过滤并评分后的职位列表(按总分降序排序)
*/
async filter_jobs_by_rules(jobs, filterRules) {
const {
minSalary = 0,
maxSalary = 0,
keywords = [],
excludeKeywords = [],
accountConfig = {}, // 账号配置
resumeInfo = {} // 简历信息
} = filterRules;
// 关键词从职位类型配置中获取,不再从账号配置中获取
let filterKeywords = keywords || [];
let excludeKeywordsList = excludeKeywords || [];
// 使用账号配置的薪资范围
const accountMinSalary = accountConfig.min_salary || minSalary || 0;
const accountMaxSalary = accountConfig.max_salary || maxSalary || 0;
// 对每个职位进行评分
const scoredJobs = jobs.map(job => {
const jobData = job.toJSON ? job.toJSON() : job;
// 1. 距离分数30%
const locationScore = this.calculate_location_score(
jobData.location,
resumeInfo.expectedLocation
);
// 2. 关键字分数10%
const keywordScore = this.calculate_keyword_score(
jobData,
filterKeywords
);
// 3. 薪资分数20%
const salaryScore = this.calculate_salary_score(
jobData.salary,
resumeInfo.expectedSalary,
accountMinSalary,
accountMaxSalary
);
// 4. 公司规模分数20%
const companySizeScore = this.calculate_company_size_score(
jobData.companySize
);
// 计算总分(累加)
const totalScore = Math.round(
locationScore * 0.3 +
keywordScore * 0.1 +
salaryScore * 0.2 +
companySizeScore * 0.2
);
return {
...jobData,
matchScore: totalScore,
scoreDetails: {
locationScore,
keywordScore,
salaryScore,
companySizeScore,
totalScore
}
};
});
// 过滤:排除关键词、最低分数、按总分排序
return scoredJobs
.filter(job => {
// 1. 排除关键词过滤
if (excludeKeywordsList && excludeKeywordsList.length > 0) {
const jobText = `${job.jobTitle} ${job.companyName} ${job.jobDescription || ''}`.toLowerCase();
const hasExcluded = excludeKeywordsList.some(kw => jobText.includes(kw.toLowerCase()));
if (hasExcluded) {
return false;
}
}
// 2. 最低分数过滤(总分 >= 60 才投递)
if (job.matchScore < 60) {
return false;
}
return true;
})
.sort((a, b) => b.matchScore - a.matchScore); // 按总分降序排序
}
/**
* 计算距离分数0-100分
* @param {string} jobLocation - 职位工作地点
* @param {string} expectedLocation - 期望工作地点
* @returns {number} 距离分数
*/
calculate_location_score(jobLocation, expectedLocation) {
if (!jobLocation) return 50; // 没有地点信息,给中等分数
if (!expectedLocation) return 70; // 没有期望地点,给较高分数
const jobLoc = jobLocation.toLowerCase().trim();
const expectedLoc = expectedLocation.toLowerCase().trim();
// 完全匹配
if (jobLoc === expectedLoc) {
return 100;
}
// 包含匹配(如:期望"北京",职位"北京朝阳区"
if (jobLoc.includes(expectedLoc) || expectedLoc.includes(jobLoc)) {
return 90;
}
// 城市匹配(提取城市名)
const jobCity = this.extract_city(jobLoc);
const expectedCity = this.extract_city(expectedLoc);
if (jobCity && expectedCity && jobCity === expectedCity) {
return 80;
}
// 部分匹配(如:都包含"北京"
if (jobLoc.includes('北京') && expectedLoc.includes('北京')) {
return 70;
}
if (jobLoc.includes('上海') && expectedLoc.includes('上海')) {
return 70;
}
if (jobLoc.includes('深圳') && expectedLoc.includes('深圳')) {
return 70;
}
if (jobLoc.includes('广州') && expectedLoc.includes('广州')) {
return 70;
}
// 不匹配
return 30;
}
/**
* 提取城市名
* @param {string} location - 地点字符串
* @returns {string|null} 城市名
*/
extract_city(location) {
const cities = ['北京', '上海', '深圳', '广州', '杭州', '成都', '南京', '武汉', '西安', '苏州'];
for (const city of cities) {
if (location.includes(city)) {
return city;
}
}
return null;
}
/**
* 计算关键字分数0-100分
* @param {object} jobData - 职位数据
* @param {Array} filterKeywords - 过滤关键词列表
* @returns {number} 关键字分数
*/
calculate_keyword_score(jobData, filterKeywords) {
if (!filterKeywords || filterKeywords.length === 0) {
return 70; // 没有关键词要求,给较高分数
}
const jobText = `${jobData.jobTitle} ${jobData.companyName} ${jobData.jobDescription || ''} ${jobData.keyword || ''}`.toLowerCase();
// 计算匹配的关键词数量
let matchedCount = 0;
filterKeywords.forEach(kw => {
if (jobText.includes(kw.toLowerCase())) {
matchedCount++;
}
});
// 计算匹配度百分比
const matchRate = matchedCount / filterKeywords.length;
return Math.round(matchRate * 100);
}
/**
* 计算薪资分数0-100分
* @param {string} jobSalary - 职位薪资
* @param {string} expectedSalary - 期望薪资
* @param {number} accountMinSalary - 账号配置最低薪资
* @param {number} accountMaxSalary - 账号配置最高薪资
* @returns {number} 薪资分数
*/
calculate_salary_score(jobSalary, expectedSalary, accountMinSalary, accountMaxSalary) {
if (!jobSalary) return 50; // 没有薪资信息,给中等分数
const salaryRange = this.parse_salary_range(jobSalary);
if (!salaryRange || salaryRange.min === 0) return 50;
const avgJobSalary = (salaryRange.min + salaryRange.max) / 2;
// 优先使用账号配置的薪资范围
if (accountMinSalary > 0 || accountMaxSalary > 0) {
if (accountMinSalary > 0 && salaryRange.max < accountMinSalary) {
return 20; // 职位最高薪资低于账号最低要求
}
if (accountMaxSalary > 0 && salaryRange.min > accountMaxSalary) {
return 20; // 职位最低薪资高于账号最高要求
}
// 在范围内,根据薪资水平给分
if (avgJobSalary >= accountMinSalary && (accountMaxSalary === 0 || avgJobSalary <= accountMaxSalary)) {
return 100; // 完全符合要求
}
}
// 如果有期望薪资,与期望薪资比较
if (expectedSalary) {
const expected = this.parse_expected_salary(expectedSalary);
if (expected) {
const ratio = expected / avgJobSalary;
if (ratio <= 0.8) {
return 100; // 期望薪资低于职位薪资,完全匹配
} else if (ratio <= 1.0) {
return 90; // 期望薪资略低于或等于职位薪资
} else if (ratio <= 1.2) {
return 70; // 期望薪资略高于职位薪资
} else {
return 50; // 期望薪资明显高于职位薪资
}
}
}
// 没有期望薪资和账号配置,给中等分数
return 60;
}
/**
* 解析期望薪资
* @param {string} expectedSalary - 期望薪资描述
* @returns {number|null} 期望薪资数值(元)
*/
parse_expected_salary(expectedSalary) {
if (!expectedSalary) return null;
// 匹配数字+K格式20K
const match = expectedSalary.match(/(\d+)[kK千]/);
if (match) {
return parseInt(match[1]) * 1000;
}
// 匹配纯数字20000
const numMatch = expectedSalary.match(/(\d+)/);
if (numMatch) {
return parseInt(numMatch[1]);
}
return null;
}
/**
* 计算公司规模分数0-100分
* @param {string} companySize - 公司规模
* @returns {number} 公司规模分数
*/
calculate_company_size_score(companySize) {
if (!companySize) return 60; // 没有规模信息,给中等分数
const size = companySize.toLowerCase();
// 大型公司1000人以上给高分
if (size.includes('1000') || size.includes('1000+') || size.includes('1000人以上')) {
return 100;
}
if (size.includes('500-1000') || size.includes('500人以上')) {
return 90;
}
// 中型公司100-500人给中高分
if (size.includes('100-500') || size.includes('100人以上')) {
return 80;
}
if (size.includes('50-100')) {
return 70;
}
// 小型公司50人以下给中低分
if (size.includes('20-50') || size.includes('20人以上')) {
return 60;
}
if (size.includes('0-20') || size.includes('20人以下')) {
return 50;
}
// 默认中等分数
return 60;
}
/**
* 解析薪资范围
* @param {string} salaryDesc - 薪资描述(如 "15-25K·14薪"
* @returns {object} 薪资范围 { min, max }
*/
parse_salary_range(salaryDesc) {
if (!salaryDesc) return { min: 0, max: 0 };
// 匹配常见格式15-25K, 15K-25K, 15-25k·14薪
const match = salaryDesc.match(/(\d+)[-~](\d+)[kK]/);
if (match) {
return {
min: parseInt(match[1]) * 1000,
max: parseInt(match[2]) * 1000
};
}
// 匹配单个数值25K
const singleMatch = salaryDesc.match(/(\d+)[kK]/);
if (singleMatch) {
const value = parseInt(singleMatch[1]) * 1000;
return { min: value, max: value };
}
return { min: 0, max: 0 };
}
/**
* 延迟函数
* @param {number} ms - 毫秒数
* @returns {Promise<void>}
*/
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
module.exports = new JobManager();