1
This commit is contained in:
555
api/middleware/job/resumeManager.js
Normal file
555
api/middleware/job/resumeManager.js
Normal file
@@ -0,0 +1,555 @@
|
||||
const aiService = require('./aiService');
|
||||
const jobFilterService = require('./job_filter_service');
|
||||
const logs = require('../logProxy');
|
||||
const db = require('../dbProxy');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
|
||||
/**
|
||||
* 简历管理模块
|
||||
* 负责简历获取、分析、存储和匹配度计算
|
||||
*/
|
||||
class ResumeManager {
|
||||
constructor() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户在线简历
|
||||
* @param {string} sn_code - 设备SN码
|
||||
* @param {object} mqttClient - MQTT客户端
|
||||
* @param {object} params - 参数
|
||||
* @returns {Promise<object>} 简历信息
|
||||
*/
|
||||
async get_online_resume(sn_code, mqttClient, params = {}) {
|
||||
console.log(`[简历管理] 开始获取设备 ${sn_code} 的在线简历`);
|
||||
|
||||
const { platform = 'boss' } = params;
|
||||
|
||||
// 通过MQTT指令获取简历信息
|
||||
const response = await mqttClient.publishAndWait(sn_code, {
|
||||
platform: platform,
|
||||
action: 'get_online_resume',
|
||||
data: {}
|
||||
});
|
||||
|
||||
if (!response || response.code !== 200) {
|
||||
console.error(`[简历管理] 获取简历失败:`, response);
|
||||
throw new Error(response?.message || '获取简历失败');
|
||||
}
|
||||
|
||||
const resumeData = response.data;
|
||||
console.log(`[简历管理] 成功获取简历数据:`, resumeData);
|
||||
|
||||
// 存储用户在线简历数据到数据库
|
||||
try {
|
||||
await this.save_resume_to_database(sn_code, platform, resumeData);
|
||||
console.log(`[简历管理] 简历数据已保存到数据库`);
|
||||
} catch (error) {
|
||||
console.error(`[简历管理] 保存简历数据失败:`, error);
|
||||
// 不抛出错误,继续返回数据
|
||||
}
|
||||
|
||||
return resumeData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存简历数据到数据库
|
||||
* @param {string} sn_code - 设备SN码
|
||||
* @param {string} platform - 平台名称
|
||||
* @param {object} resumeData - 简历数据
|
||||
* @returns {Promise<object>} 保存结果
|
||||
*/
|
||||
async save_resume_to_database(sn_code, platform, resumeData) {
|
||||
const resume_info = db.getModel('resume_info');
|
||||
const pla_account = db.getModel('pla_account');
|
||||
|
||||
// 通过 sn_code 查询对应的 pla_account 获取 account_id
|
||||
const account = await pla_account.findOne({
|
||||
where: { sn_code, platform_type: platform }
|
||||
});
|
||||
|
||||
if (!account) {
|
||||
throw new Error(`未找到设备 ${sn_code} 在平台 ${platform} 的账户信息`);
|
||||
}
|
||||
|
||||
// 确保 account_id 是字符串类型(模型定义为 STRING(50))
|
||||
const account_id = String(account.id || ''); // pla_account 的自增ID,转换为字符串
|
||||
|
||||
// 提取基本信息
|
||||
const baseInfo = resumeData.baseInfo || {};
|
||||
const expectList = resumeData.expectList || [];
|
||||
const workExpList = resumeData.workExpList || [];
|
||||
const projectExpList = resumeData.projectExpList || [];
|
||||
const educationExpList = resumeData.educationExpList || [];
|
||||
const userDesc = resumeData.userDesc || '';
|
||||
|
||||
// 查找是否已存在简历
|
||||
const existingResume = await resume_info.findOne({
|
||||
where: { sn_code, platform, isActive: true }
|
||||
});
|
||||
|
||||
// 如果已存在简历,使用数据库中的 resumeId(可能是字符串或数字)
|
||||
// 如果不存在,生成新的 UUID 字符串
|
||||
const resumeId = existingResume ? String(existingResume.resumeId || existingResume.id || uuidv4()) : uuidv4();
|
||||
|
||||
// 提取技能标签(从个人优势中提取)
|
||||
const skills = this.extract_skills_from_desc(userDesc);
|
||||
|
||||
// 获取期望信息
|
||||
const expectInfo = expectList[0] || {};
|
||||
|
||||
// 获取最新工作经验
|
||||
const latestWork = workExpList[0] || {};
|
||||
|
||||
// 获取最高学历
|
||||
const highestEdu = educationExpList[0] || {};
|
||||
|
||||
// 构建简历信息对象
|
||||
const resumeInfo = {
|
||||
sn_code,
|
||||
account_id, // 使用 pla_account 的自增ID
|
||||
platform,
|
||||
resumeId: resumeId,
|
||||
// 个人信息
|
||||
fullName: String(baseInfo.name || ''),
|
||||
gender: baseInfo.gender === 1 ? '男' : baseInfo.gender === 0 ? '女' : '',
|
||||
age: parseInt(String(baseInfo.age || 0), 10) || 0, // 确保是整数
|
||||
phone: String(baseInfo.account || ''),
|
||||
email: String(baseInfo.emailBlur || ''),
|
||||
location: String(expectInfo.locationName || ''),
|
||||
|
||||
// 教育背景
|
||||
education: String(highestEdu.degreeName || baseInfo.degreeCategory || ''),
|
||||
major: String(highestEdu.major || ''),
|
||||
school: String(highestEdu.school || ''),
|
||||
graduationYear: parseInt(String(highestEdu.endYear || 0), 10) || 0, // 确保是整数
|
||||
|
||||
// 工作经验
|
||||
workYears: baseInfo.workYearDesc || `${baseInfo.workYears || 0}年`,
|
||||
currentPosition: latestWork.positionName || '',
|
||||
currentCompany: latestWork.companyName || '',
|
||||
currentSalary: '',
|
||||
|
||||
// 期望信息
|
||||
expectedPosition: expectInfo.positionName || '',
|
||||
expectedSalary: expectInfo.salaryDesc || '',
|
||||
expectedLocation: expectInfo.locationName || '',
|
||||
expectedIndustry: expectInfo.industryDesc || '',
|
||||
|
||||
// 技能和专长
|
||||
skills: JSON.stringify(skills),
|
||||
skillDescription: userDesc,
|
||||
certifications: JSON.stringify(resumeData.certificationList || []),
|
||||
|
||||
// 项目经验
|
||||
projectExperience: JSON.stringify(projectExpList.map(p => ({
|
||||
name: p.name,
|
||||
role: p.roleName,
|
||||
startDate: p.startDate,
|
||||
endDate: p.endDate,
|
||||
description: p.projectDesc,
|
||||
performance: p.performance
|
||||
}))),
|
||||
|
||||
// 工作经历
|
||||
workExperience: JSON.stringify(workExpList.map(w => ({
|
||||
company: w.companyName,
|
||||
position: w.positionName,
|
||||
startDate: w.startDate,
|
||||
endDate: w.endDate,
|
||||
content: w.workContent,
|
||||
industry: w.industry?.name
|
||||
}))),
|
||||
|
||||
// 简历内容
|
||||
resumeContent: userDesc,
|
||||
|
||||
// 原始数据
|
||||
originalData: JSON.stringify(resumeData),
|
||||
|
||||
// 状态信息
|
||||
isActive: true,
|
||||
isPublic: true,
|
||||
syncTime: new Date()
|
||||
};
|
||||
|
||||
// 保存或更新简历
|
||||
if (existingResume) {
|
||||
await resume_info.update(resumeInfo, { where: { resumeId: resumeId } });
|
||||
console.log(`[简历管理] 简历已更新 - ID: ${resumeId}`);
|
||||
} else {
|
||||
await resume_info.create(resumeInfo);
|
||||
console.log(`[简历管理] 简历已创建 - ID: ${resumeId}`);
|
||||
}
|
||||
|
||||
// 二期规划:AI 分析暂时禁用,使用简单的文本匹配
|
||||
console.log(`[简历管理] AI分析已禁用(二期规划),使用文本匹配过滤`);
|
||||
|
||||
return { resumeId, message: existingResume ? '简历更新成功' : '简历创建成功' };
|
||||
}
|
||||
|
||||
/**
|
||||
* 从描述中提取技能标签
|
||||
* @param {string} description - 描述文本
|
||||
* @returns {Array} 技能标签数组
|
||||
*/
|
||||
extract_skills_from_desc(description) {
|
||||
const skills = [];
|
||||
const commonSkills = [
|
||||
'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'
|
||||
];
|
||||
|
||||
commonSkills.forEach(skill => {
|
||||
if (description.includes(skill)) {
|
||||
skills.push(skill);
|
||||
}
|
||||
});
|
||||
|
||||
return [...new Set(skills)]; // 去重
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用AI分析简历
|
||||
* @param {string} resumeId - 简历ID
|
||||
* @param {object} resumeInfo - 简历信息
|
||||
* @returns {Promise<object>} 分析结果
|
||||
*/
|
||||
async analyze_resume_with_ai(resumeId, resumeInfo) {
|
||||
console.log(`[简历管理] 开始AI分析简历 - ID: ${resumeId}`);
|
||||
|
||||
const resume_info = db.getModel('resume_info');
|
||||
|
||||
// 构建分析提示词
|
||||
const prompt = `请分析以下简历,提供专业的评估:
|
||||
|
||||
姓名:${resumeInfo.fullName}
|
||||
工作年限:${resumeInfo.workYears}
|
||||
当前职位:${resumeInfo.currentPosition}
|
||||
期望职位:${resumeInfo.expectedPosition}
|
||||
期望薪资:${resumeInfo.expectedSalary}
|
||||
学历:${resumeInfo.education}
|
||||
技能:${resumeInfo.skills}
|
||||
|
||||
个人优势:
|
||||
${resumeInfo.skillDescription}
|
||||
|
||||
请从以下几个方面进行分析:
|
||||
1. 核心技能标签(提取5-10个关键技能)
|
||||
2. 优势分析(100字以内)
|
||||
3. 劣势分析(100字以内)
|
||||
4. 职业建议(150字以内)
|
||||
5. 竞争力评分(0-100分)`;
|
||||
|
||||
try {
|
||||
// 调用AI服务进行分析
|
||||
const aiAnalysis = await aiService.analyzeResume(prompt);
|
||||
|
||||
// 解析AI返回的结果
|
||||
const analysis = this.parse_ai_analysis(aiAnalysis, resumeInfo);
|
||||
|
||||
// 更新简历的AI分析字段
|
||||
await resume_info.update({
|
||||
aiSkillTags: JSON.stringify(analysis.skillTags),
|
||||
aiStrengths: analysis.strengths,
|
||||
aiWeaknesses: analysis.weaknesses,
|
||||
aiCareerSuggestion: analysis.careerSuggestion,
|
||||
aiCompetitiveness: analysis.competitiveness
|
||||
}, { where: { id: resumeId } });
|
||||
|
||||
console.log(`[简历管理] AI分析完成 - 竞争力评分: ${analysis.competitiveness}`);
|
||||
|
||||
return analysis;
|
||||
} catch (error) {
|
||||
console.error(`[简历管理] AI分析失败:`, error, {
|
||||
resumeId: resumeId,
|
||||
fullName: resumeInfo.fullName
|
||||
});
|
||||
|
||||
// 如果AI分析失败,使用基于规则的默认分析
|
||||
const defaultAnalysis = this.get_default_analysis(resumeInfo);
|
||||
|
||||
return defaultAnalysis;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析AI分析结果
|
||||
* @param {object} aiResponse - AI响应对象
|
||||
* @param {object} resumeInfo - 简历信息
|
||||
* @returns {object} 解析后的分析结果
|
||||
*/
|
||||
parse_ai_analysis(aiResponse, resumeInfo) {
|
||||
try {
|
||||
// 尝试从AI响应中解析JSON
|
||||
const content = aiResponse.content || aiResponse.analysis?.content || '';
|
||||
|
||||
// 如果AI返回的是JSON格式
|
||||
if (content.includes('{') && content.includes('}')) {
|
||||
const jsonMatch = content.match(/\{[\s\S]*\}/);
|
||||
if (jsonMatch) {
|
||||
const parsed = JSON.parse(jsonMatch[0]);
|
||||
|
||||
return {
|
||||
skillTags: parsed.skillTags || parsed.技能标签 || [],
|
||||
strengths: parsed.strengths || parsed.优势 || parsed.优势分析 || '',
|
||||
weaknesses: parsed.weaknesses || parsed.劣势 || parsed.劣势分析 || '',
|
||||
careerSuggestion: parsed.careerSuggestion || parsed.职业建议 || '',
|
||||
competitiveness: parsed.competitiveness || parsed.竞争力评分 || 70
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 如果无法解析JSON,尝试从文本中提取信息
|
||||
const skillTagsMatch = content.match(/技能标签[::](.*?)(?:\n|$)/);
|
||||
const strengthsMatch = content.match(/优势[分析]*[::](.*?)(?:\n|劣势)/s);
|
||||
const weaknessesMatch = content.match(/劣势[分析]*[::](.*?)(?:\n|职业)/s);
|
||||
const suggestionMatch = content.match(/职业建议[::](.*?)(?:\n|竞争力)/s);
|
||||
const scoreMatch = content.match(/竞争力评分[::](\d+)/);
|
||||
|
||||
return {
|
||||
skillTags: skillTagsMatch ? skillTagsMatch[1].split(/[,,、]/).map(s => s.trim()) : [],
|
||||
strengths: strengthsMatch ? strengthsMatch[1].trim() : '',
|
||||
weaknesses: weaknessesMatch ? weaknessesMatch[1].trim() : '',
|
||||
careerSuggestion: suggestionMatch ? suggestionMatch[1].trim() : '',
|
||||
competitiveness: scoreMatch ? parseInt(scoreMatch[1]) : 70
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(`[简历管理] 解析AI分析结果失败:`, error);
|
||||
// 解析失败时使用默认分析
|
||||
return this.get_default_analysis(resumeInfo);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认分析结果(基于规则)
|
||||
* @param {object} resumeInfo - 简历信息
|
||||
* @returns {object} 分析结果
|
||||
*/
|
||||
get_default_analysis(resumeInfo) {
|
||||
const skills = JSON.parse(resumeInfo.skills || '[]');
|
||||
const workYears = parseInt(resumeInfo.workYears) || 0;
|
||||
|
||||
// 计算竞争力评分
|
||||
let competitiveness = 50; // 基础分
|
||||
|
||||
// 工作年限加分
|
||||
if (workYears >= 10) competitiveness += 20;
|
||||
else if (workYears >= 5) competitiveness += 15;
|
||||
else if (workYears >= 3) competitiveness += 10;
|
||||
|
||||
// 技能数量加分
|
||||
if (skills.length >= 10) competitiveness += 15;
|
||||
else if (skills.length >= 5) competitiveness += 10;
|
||||
|
||||
// 学历加分
|
||||
if (resumeInfo.education.includes('硕士')) competitiveness += 10;
|
||||
else if (resumeInfo.education.includes('本科')) competitiveness += 5;
|
||||
|
||||
// 确保分数在0-100之间
|
||||
competitiveness = Math.min(100, Math.max(0, competitiveness));
|
||||
|
||||
return {
|
||||
skillTags: skills.slice(0, 10),
|
||||
strengths: `拥有${resumeInfo.workYears}工作经验,技术栈全面,掌握${skills.slice(0, 5).join('、')}等主流技术。`,
|
||||
weaknesses: '建议继续深化技术深度,关注行业前沿技术发展。',
|
||||
careerSuggestion: `基于您的${resumeInfo.expectedPosition}职业目标和${resumeInfo.workYears}经验,建议重点关注中大型互联网公司的相关岗位,期望薪资${resumeInfo.expectedSalary}较为合理。`,
|
||||
competitiveness: competitiveness
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析简历要素
|
||||
* @param {string} sn_code - 设备SN码
|
||||
* @param {object} resumeData - 简历数据
|
||||
* @returns {Promise<object>} 分析结果
|
||||
*/
|
||||
async analyze_resume(sn_code, resumeData) {
|
||||
console.log(`[简历管理] 开始分析设备 ${sn_code} 的简历要素`);
|
||||
console.log(`[简历管理] AI分析已禁用(二期规划),使用文本匹配过滤`);
|
||||
|
||||
// 构建简历文本
|
||||
const resumeText = this.build_resume_text(resumeData);
|
||||
|
||||
// 二期规划:AI 分析暂时禁用,使用简单的文本匹配
|
||||
// const analysis = await aiService.analyzeResume(resumeText);
|
||||
|
||||
// 使用简单的文本匹配提取技能
|
||||
const skills = this.extract_skills_from_desc(resumeData.skillDescription || resumeText);
|
||||
|
||||
const result = {
|
||||
sn_code: sn_code,
|
||||
resumeData: resumeData,
|
||||
analysis: {
|
||||
skills: skills,
|
||||
content: '文本匹配分析(AI分析已禁用,二期规划)'
|
||||
},
|
||||
timestamp: Date.now()
|
||||
};
|
||||
|
||||
console.log(`[简历管理] 简历分析完成,提取技能: ${skills.join(', ')}`);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建简历文本
|
||||
* @param {object} resumeData - 简历数据
|
||||
* @returns {string} 简历文本
|
||||
*/
|
||||
build_resume_text(resumeData) {
|
||||
const parts = [];
|
||||
|
||||
// 基本信息
|
||||
if (resumeData.basicInfo) {
|
||||
parts.push(`基本信息:${JSON.stringify(resumeData.basicInfo)}`);
|
||||
}
|
||||
|
||||
// 技能标签
|
||||
if (resumeData.skills && resumeData.skills.length > 0) {
|
||||
parts.push(`技能标签:${resumeData.skills.join(', ')}`);
|
||||
}
|
||||
|
||||
// 工作经验
|
||||
if (resumeData.workExperience && resumeData.workExperience.length > 0) {
|
||||
const workExp = resumeData.workExperience.map(exp =>
|
||||
`${exp.company} - ${exp.position} (${exp.duration})`
|
||||
).join('; ');
|
||||
parts.push(`工作经验:${workExp}`);
|
||||
}
|
||||
|
||||
// 项目经验
|
||||
if (resumeData.projectExperience && resumeData.projectExperience.length > 0) {
|
||||
const projectExp = resumeData.projectExperience.map(project =>
|
||||
`${project.name} - ${project.description}`
|
||||
).join('; ');
|
||||
parts.push(`项目经验:${projectExp}`);
|
||||
}
|
||||
|
||||
// 教育背景
|
||||
if (resumeData.education) {
|
||||
parts.push(`教育背景:${resumeData.education.school} - ${resumeData.education.major} - ${resumeData.education.degree}`);
|
||||
}
|
||||
|
||||
// 期望信息
|
||||
if (resumeData.expectations) {
|
||||
parts.push(`期望薪资:${resumeData.expectations.salary}`);
|
||||
parts.push(`期望地点:${resumeData.expectations.location}`);
|
||||
}
|
||||
|
||||
return parts.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算简历与岗位的匹配度
|
||||
* @param {string} sn_code - 设备SN码
|
||||
* @param {object} jobInfo - 岗位信息
|
||||
* @returns {Promise<object>} 匹配度分析结果
|
||||
*/
|
||||
async calculate_match_score(sn_code, jobInfo) {
|
||||
console.log(`[简历管理] 开始计算设备 ${sn_code} 与岗位的匹配度`);
|
||||
console.log(`[简历管理] 使用文本匹配分析(AI分析已禁用,二期规划)`);
|
||||
|
||||
// 获取简历分析结果
|
||||
const resumeAnalysis = await this.get_resume_analysis(sn_code);
|
||||
|
||||
// 获取账号的职位类型ID
|
||||
let jobTypeId = null;
|
||||
try {
|
||||
const db = require('../dbProxy.js');
|
||||
const pla_account = db.getModel('pla_account');
|
||||
if (pla_account) {
|
||||
const account = await pla_account.findOne({
|
||||
where: { sn_code, platform_type: jobInfo.platform || 'boss' }
|
||||
});
|
||||
if (account) {
|
||||
jobTypeId = account.job_type_id;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`[简历管理] 获取职位类型ID失败:`, error);
|
||||
}
|
||||
|
||||
// 使用文本匹配进行岗位分析(替代 AI)
|
||||
const jobAnalysis = await jobFilterService.analyzeJobMatch(jobInfo, resumeAnalysis.analysis || {}, jobTypeId);
|
||||
|
||||
const result = {
|
||||
sn_code: sn_code,
|
||||
jobInfo: jobInfo,
|
||||
resumeAnalysis: resumeAnalysis.analysis,
|
||||
jobAnalysis: jobAnalysis,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
|
||||
console.log(`[简历管理] 匹配度分析完成,综合分数: ${jobAnalysis.overallScore}`);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取简历分析结果
|
||||
* @param {string} sn_code - 设备SN码
|
||||
* @param {object} mqttClient - MQTT客户端(可选)
|
||||
* @returns {Promise<object>} 简历分析结果
|
||||
*/
|
||||
async get_resume_analysis(sn_code, mqttClient = null) {
|
||||
// 获取MQTT客户端
|
||||
if (!mqttClient) {
|
||||
const scheduleManager = require('../schedule');
|
||||
if (scheduleManager && scheduleManager.mqttClient) {
|
||||
mqttClient = scheduleManager.mqttClient;
|
||||
} else {
|
||||
throw new Error('MQTT客户端未初始化');
|
||||
}
|
||||
}
|
||||
|
||||
// 重新获取和分析
|
||||
const resumeData = await this.get_online_resume(sn_code, mqttClient);
|
||||
return await this.analyze_resume(sn_code, resumeData);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新简历信息
|
||||
* @param {string} sn_code - 设备SN码
|
||||
* @param {object} newResumeData - 新的简历数据
|
||||
* @returns {Promise<object>} 更新结果
|
||||
*/
|
||||
async update_resume(sn_code, newResumeData) {
|
||||
console.log(`[简历管理] 更新设备 ${sn_code} 的简历信息`);
|
||||
|
||||
// 重新分析简历
|
||||
const analysis = await this.analyze_resume(sn_code, newResumeData);
|
||||
|
||||
return {
|
||||
message: '简历更新成功',
|
||||
analysis: analysis
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取简历摘要
|
||||
* @param {string} sn_code - 设备SN码
|
||||
* @returns {Promise<object>} 简历摘要
|
||||
*/
|
||||
async get_resume_summary(sn_code) {
|
||||
const analysis = await this.get_resume_analysis(sn_code);
|
||||
|
||||
const summary = {
|
||||
sn_code: sn_code,
|
||||
skills: analysis.analysis.skills || [],
|
||||
experience: analysis.analysis.experience || '',
|
||||
education: analysis.analysis.education || '',
|
||||
expectedSalary: analysis.analysis.expectedSalary || '',
|
||||
expectedLocation: analysis.analysis.expectedLocation || '',
|
||||
lastUpdate: analysis.timestamp
|
||||
};
|
||||
|
||||
return summary;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = new ResumeManager();
|
||||
Reference in New Issue
Block a user