@@ -14,10 +14,11 @@ import java.util.stream.Collectors;
/**
* 岗位匹配度计算服务
* <p>主要功能:根据用户简历和岗位信息,计算教育/经历/技能三 维度匹配分</p>
* <p>主要功能:根据用户简历和岗位信息,计算方向/ 教育/经历/技能四 维度匹配分</p>
* <p>依赖:无</p>
* <p>使用表:bg_user_profile(查询用户简历维度数据)、bg_job_skill_tag_relation(查询岗位技能)、
* bg_user_profile_skill_tag_relation(查询用户技能)、bg_major_category(专业树形匹配)</p>
* bg_user_profile_skill_tag_relation(查询用户技能)、bg_major_category(专业树形匹配)、
* bg_user_job_intention( AI推荐岗位/行业方向)、bg_job_category(岗位类型树形匹配)、bg_industry(行业树形匹配)</p>
*
* @author zk
*/
@@ -37,9 +38,19 @@ public class JobMatchService {
@Autowired
private MajorCategoryMapper majorCategoryMapper ;
@Autowired
private UserJobIntentionMapper userJobIntentionMapper ;
@Autowired
private JobCategoryMapper jobCategoryMapper ;
@Autowired
private IndustryMapper industryMapper ;
/**
* 批量计算岗位匹配度
* <p>1. 查询用户简历 2. 查询用户技能 3. 批量查询岗位技能 4. 批量查询专业信息 5. 逐个计算三维度分数 6. 加权计算总分</p>
* <p>1. 查询用户简历 2. 查询用户技能 3. 批量查询岗位技能 4. 批量查询专业信息
* 5. 查询求职意向(AI推荐方向)并加载类型/行业节点 6. 逐个计算四维度分数 7. 加权计算总分</p>
*/
public Map < Long , Map < String , Integer > > batchCalculateMatchScore ( List < JobListItemVo > jobs , Long userId ) {
if ( jobs = = null | | jobs . isEmpty ( ) ) {
@@ -64,20 +75,29 @@ public class JobMatchService {
// 4. 批量查询专业信息(用于树形匹配)
Map < Long , MajorCategory > majorMap = loadMajorMap ( profile , jobs ) ;
// 5. 逐个计算匹配度
// 5. 查询求职意向(取 AI 推荐岗位方向/行业方向),并加载相关类型/行业节点
UserJobIntention intention = userJobIntentionMapper . selectOne ( new LambdaQueryWrapper < UserJobIntention > ( ) . eq ( UserJobIntention : : getUserId , userId ) ) ;
List < Long > aiCategoryIds = intention ! = null ? intention . getAiCategoryIds ( ) : null ;
List < Long > aiIndustryIds = intention ! = null ? intention . getAiIndustryIds ( ) : null ;
Map < Long , JobCategory > categoryMap = loadCategoryMap ( aiCategoryIds , jobs ) ;
Map < Long , Industry > industryMap = loadIndustryMap ( aiIndustryIds , jobs ) ;
// 6. 逐个计算匹配度
Map < Long , Map < String , Integer > > result = new HashMap < > ( ) ;
for ( JobListItemVo job : jobs ) {
int educationScore = calculateEducationScore ( profile , job , majorMap ) ;
int experienceScore = calculateExperienceScore ( profile ) ;
int skillScore = calculateSkillScore ( jobSkillMap . get ( job . getId ( ) ) , userSkillTagSet ) ;
int fitScore = calculateFitScore ( job , aiCategoryIds , aiIndustryIds , categoryMap , industryMap ) ;
// 加权计算总分:教育30 % + 经历30 % + 技能4 0%
int totalScore = ( int ) Math . round ( educationScore * 0 . 3 + experienceScore * 0 . 3 + skillScore * 0 . 4 ) ;
// 加权计算总分:方向50% + 教育15 % + 经历15 % + 技能2 0%
int totalScore = ( int ) Math . round ( fitScore * 0 . 5 + educationScore * 0 . 15 + experienceScore * 0 . 15 + skillScore * 0 . 2 ) ;
Map < String , Integer > map = new HashMap < > ( ) ;
map . put ( " educationScore " , educationScore ) ;
map . put ( " experienceScore " , experienceScore ) ;
map . put ( " skillScore " , skillScore ) ;
map . put ( " fitScore " , fitScore ) ;
map . put ( " totalScore " , totalScore ) ;
result . put ( job . getId ( ) , map ) ;
}
@@ -85,6 +105,129 @@ public class JobMatchService {
return result ;
}
/**
* 批量加载岗位类型节点Map(AI推荐类型 + 岗位类型,用于层级匹配)
*/
private Map < Long , JobCategory > loadCategoryMap ( List < Long > aiCategoryIds , List < JobListItemVo > jobs ) {
Set < Long > ids = new HashSet < > ( ) ;
if ( aiCategoryIds ! = null ) {
ids . addAll ( aiCategoryIds ) ;
}
for ( JobListItemVo job : jobs ) {
if ( job . getCategoryId ( ) ! = null ) {
ids . add ( job . getCategoryId ( ) ) ;
}
}
if ( ids . isEmpty ( ) ) {
return Collections . emptyMap ( ) ;
}
List < JobCategory > categories = jobCategoryMapper . selectBatchIds ( ids ) ;
return categories . stream ( ) . collect ( Collectors . toMap ( JobCategory : : getId , c - > c ) ) ;
}
/**
* 批量加载行业节点Map(AI推荐行业 + 公司行业,用于层级匹配)
*/
private Map < Long , Industry > loadIndustryMap ( List < Long > aiIndustryIds , List < JobListItemVo > jobs ) {
Set < Long > ids = new HashSet < > ( ) ;
if ( aiIndustryIds ! = null ) {
ids . addAll ( aiIndustryIds ) ;
}
for ( JobListItemVo job : jobs ) {
if ( job . getCompanyIndustryId ( ) ! = null ) {
ids . add ( job . getCompanyIndustryId ( ) ) ;
}
}
if ( ids . isEmpty ( ) ) {
return Collections . emptyMap ( ) ;
}
List < Industry > industries = industryMapper . selectBatchIds ( ids ) ;
return industries . stream ( ) . collect ( Collectors . toMap ( Industry : : getId , i - > i ) ) ;
}
/**
* 计算方向适配得分 fitScore(百分制)
* <p>基于 AI 推荐的岗位方向/行业方向与岗位实际类型/公司行业做层级匹配</p>
* <p>fitScore = 岗位类型分×70% + 行业分×30%</p>
* <p>用户未做过 AI 分析(无 AI 推荐方向)时返回 0</p>
*/
private int calculateFitScore ( JobListItemVo job , List < Long > aiCategoryIds , List < Long > aiIndustryIds ,
Map < Long , JobCategory > categoryMap , Map < Long , Industry > industryMap ) {
boolean noCategory = aiCategoryIds = = null | | aiCategoryIds . isEmpty ( ) ;
boolean noIndustry = aiIndustryIds = = null | | aiIndustryIds . isEmpty ( ) ;
// 无任何 AI 推荐方向 → 不参与加分
if ( noCategory & & noIndustry ) {
return 0 ;
}
int categoryScore = calculateCategoryFit ( aiCategoryIds , job . getCategoryId ( ) , categoryMap ) ;
int industryScore = calculateIndustryFit ( aiIndustryIds , job . getCompanyIndustryId ( ) , industryMap ) ;
return ( int ) Math . round ( categoryScore * 0 . 7 + industryScore * 0 . 3 ) ;
}
/**
* 计算岗位类型方向匹配分(三级树,取 AI 推荐中的最高分)
* <p>同三级→100,同二级→70,同一级→30,否则→0</p>
*/
private int calculateCategoryFit ( List < Long > aiCategoryIds , Long jobCategoryId , Map < Long , JobCategory > categoryMap ) {
if ( aiCategoryIds = = null | | aiCategoryIds . isEmpty ( ) | | jobCategoryId = = null ) {
return 0 ;
}
JobCategory jobCategory = categoryMap . get ( jobCategoryId ) ;
if ( jobCategory = = null ) {
return 0 ;
}
int maxScore = 0 ;
for ( Long aiId : aiCategoryIds ) {
JobCategory aiCategory = categoryMap . get ( aiId ) ;
if ( aiCategory = = null ) continue ;
int score ;
if ( aiCategory . getId ( ) . equals ( jobCategory . getId ( ) ) ) {
score = 100 ;
} else if ( aiCategory . getParentId ( ) ! = null & & aiCategory . getParentId ( ) . equals ( jobCategory . getParentId ( ) ) ) {
score = 70 ;
} else if ( aiCategory . getRootId ( ) ! = null & & aiCategory . getRootId ( ) . equals ( jobCategory . getRootId ( ) ) ) {
score = 30 ;
} else {
score = 0 ;
}
maxScore = Math . max ( maxScore , score ) ;
if ( maxScore = = 100 ) return 100 ;
}
return maxScore ;
}
/**
* 计算行业方向匹配分(两级树,取 AI 推荐中的最高分)
* <p>同二级→100,同一级→60,否则→0</p>
*/
private int calculateIndustryFit ( List < Long > aiIndustryIds , Long companyIndustryId , Map < Long , Industry > industryMap ) {
if ( aiIndustryIds = = null | | aiIndustryIds . isEmpty ( ) | | companyIndustryId = = null ) {
return 0 ;
}
Industry companyIndustry = industryMap . get ( companyIndustryId ) ;
if ( companyIndustry = = null ) {
return 0 ;
}
int maxScore = 0 ;
for ( Long aiId : aiIndustryIds ) {
Industry aiIndustry = industryMap . get ( aiId ) ;
if ( aiIndustry = = null ) continue ;
int score ;
if ( aiIndustry . getId ( ) . equals ( companyIndustry . getId ( ) ) ) {
score = 100 ;
} else if ( aiIndustry . getParentId ( ) ! = null & & aiIndustry . getParentId ( ) . equals ( companyIndustry . getParentId ( ) ) ) {
score = 60 ;
} else {
score = 0 ;
}
maxScore = Math . max ( maxScore , score ) ;
if ( maxScore = = 100 ) return 100 ;
}
return maxScore ;
}
/**
* 批量加载专业信息Map
*/